OSDN Git Service

htmlsc(): Just sugar for htmlspecialchars(), and a foundation
[pukiwiki/pukiwiki.git] / plugin / tracker.inc.php
index 09869bb..10af617 100644 (file)
@@ -1,37 +1,51 @@
 <?php
 // PukiWiki - Yet another WikiWikiWeb clone
-// $Id: tracker.inc.php,v 1.110 2007/10/12 16:31:26 henoheno Exp $
+// $Id: tracker.inc.php,v 1.124 2011/01/25 15:01:01 henoheno Exp $
 // Copyright (C) 2003-2005, 2007 PukiWiki Developers Team
 // License: GPL v2 or (at your option) any later version
 //
 // Issue tracker plugin (See Also bugtrack plugin)
 
-define('PLUGIN_TRACKER_USAGE',      '#tracker([config[/form][,basepage]])');
-define('PLUGIN_TRACKER_LIST_USAGE', '#tracker_list([config[/list]][[,base][,field:sort[;field:sort ...][,limit]]])');
-
-define('PLUGIN_TRACKER_DEFAULT_CONFIG', 'default');
-define('PLUGIN_TRACKER_DEFAULT_FORM',   'form');
-define('PLUGIN_TRACKER_DEFAULT_LIST',   'list');
-define('PLUGIN_TRACKER_DEFAULT_LIMIT',  0 );   // 0 = Unlimited
-define('PLUGIN_TRACKER_DEFAULT_ORDER',  '');   // Example: '_real'
-
-// Allow N columns sorted at a time
-define('PLUGIN_TRACKER_LIST_SORT_LIMIT', 3);
 
-// Excluding pattern
+// Tracker_list: Excluding pattern
 define('PLUGIN_TRACKER_LIST_EXCLUDE_PATTERN','#^SubMenu$|/#'); // 'SubMenu' and using '/'
 //define('PLUGIN_TRACKER_LIST_EXCLUDE_PATTERN','#(?!)#');              // Nothing excluded
 
-// Show error rows (can't capture columns properly)
+// Tracker_list: Show error rows (can't capture columns properly)
 define('PLUGIN_TRACKER_LIST_SHOW_ERROR_PAGE', 1);
 
+// Tracker_list: Allow N columns sorted at a time
+define('PLUGIN_TRACKER_LIST_SORT_LIMIT', 3);
+
+
 // ----
+// Basic interface and strategy
+
+define('PLUGIN_TRACKER_USAGE',      '#tracker([config[/form][,basepage]])');
+define('PLUGIN_TRACKER_LIST_USAGE', '#tracker_list([config[/list]][[,base][,field:sort[;field:sort ...][,limit]]])');
+
+// $refer  : Where the plugin had been set / Where to return back to
+//           If ($refer == '') $refer = $base;
+// $base   : "$base/nnn" will be added by plugin_tracker_action(), or will be shown by Tracker_list
+//           Compat: If ($base  == '') $base  = $refer;
+// $config : ":config/plugin/tracker/$config" will be load to the Config
+// $form   : ":config/plugin/tracker/$config/$form" will be load as template for XHTML form by Tracker_form
+// $page   : ":config/plugin/tracker/$config/$page" will be load as template for a new page written by Tracker_form
+// $list   : ":config/plugin/tracker/$config/$list" will be load as template of Tracker_list
+// $order  : "field:sort" ... i.e. "Severity:desc" means sorting the field "Severity" descendant order.
+// $limit  : Show top N rows at a time
+
+define('PLUGIN_TRACKER_DEFAULT_CONFIG', 'default');
+define('PLUGIN_TRACKER_DEFAULT_FORM',   'form');
+define('PLUGIN_TRACKER_DEFAULT_PAGE',   'page');
+define('PLUGIN_TRACKER_DEFAULT_LIST',   'list');
+define('PLUGIN_TRACKER_DEFAULT_ORDER',  '');
+define('PLUGIN_TRACKER_DEFAULT_LIMIT',  0 );   // 0 = Unlimited
 
 // Sort type
 define('PLUGIN_TRACKER_SORT_TYPE_REGULAR',       0);
 define('PLUGIN_TRACKER_SORT_TYPE_NUMERIC',       1);
 define('PLUGIN_TRACKER_SORT_TYPE_STRING',        2);
-//define('PLUGIN_TRACKER_SORT_TYPE_LOCALE_STRING', 5);
 define('PLUGIN_TRACKER_SORT_TYPE_NATURAL',       6);
 if (! defined('SORT_NATURAL')) define('SORT_NATURAL', PLUGIN_TRACKER_SORT_TYPE_NATURAL);
 
@@ -40,6 +54,7 @@ define('PLUGIN_TRACKER_SORT_ORDER_DESC',    3);
 define('PLUGIN_TRACKER_SORT_ORDER_ASC',     4);
 define('PLUGIN_TRACKER_SORT_ORDER_DEFAULT', PLUGIN_TRACKER_SORT_ORDER_ASC);
 
+// ----
 
 // Show a form
 function plugin_tracker_convert()
@@ -48,46 +63,48 @@ function plugin_tracker_convert()
 
        if (PKWK_READONLY) return ''; // Show nothing
 
-       $base = $refer = isset($vars['page']) ? $vars['page'] : '';
-       $config_name = PLUGIN_TRACKER_DEFAULT_CONFIG;
-       $form        = PLUGIN_TRACKER_DEFAULT_FORM;
-
        $args = func_get_args();
        $argc = count($args);
-       if ($argc > 2) {
-               return PLUGIN_TRACKER_USAGE . '<br />';
-       }
+       if ($argc > 2) return PLUGIN_TRACKER_USAGE . '<br />';
+
+       $base   = isset($vars['page']) ? $vars['page'] : '';
+       $refer  = '';
+       $config = '';
+       $form   = '';
+       $rel    = '';
        switch ($argc) {
        case 2:
-               $arg = get_fullname($args[1], $base);
-               if (is_pagename($arg)) $base = $arg;
+               $rel = $args[1];
                /*FALLTHROUGH*/
        case 1:
-               // Config/form
+               // Set "$config/$form"
                if ($args[0] != '') {
                        $arg = explode('/', trim($args[0]), 2);
-                       if ($arg[0] != '' ) $config_name = trim($arg[0]);
-                       if (isset($arg[1])) $form        = trim($arg[1]);
+                       if ($arg[0] != '' ) $config = trim($arg[0]);
+                       if (isset($arg[1])) $form   = trim($arg[1]);
                }
        }
        unset($args, $argc, $arg);
 
-       $config = new Config('plugin/tracker/' . $config_name);
-       if (! $config->read()) {
-               return '#tracker: Config \'' . htmlspecialchars($config_name) . '\' not found<br />';
+       $tracker_form = & new Tracker_form();
+       if (! $tracker_form->init($base, $refer, $config, $rel)) {
+               return '#tracker: ' . htmlsc($tracker_form->error) . '<br />';
        }
-       $config->config_name = $config_name;
 
-       $form     = $config->page . '/' . $form;
+       // Load $template
+       $form = ($form != '') ? $form : PLUGIN_TRACKER_DEFAULT_FORM;
+       $form = $tracker_form->config->page . '/' . $form;
        $template = plugin_tracker_get_source($form, TRUE);
        if ($template === FALSE || empty($template)) {
-               return '#tracker: Form \'' . make_pagelink($form) . '\' not found or seems empty<br />';
+               return '#tracker: Form not found: ' . $form . '<br />';
        }
 
-       $_form = & new Tracker_form($base, $refer, $config);
-       $_form->initFields(plugin_tracker_field_pickup($template));
-       $_form->initHiddenFields();
-       $fields = $_form->fields;
+       if (! $tracker_form->initFields(plugin_tracker_field_pickup($template)) ||
+               ! $tracker_form->initHiddenFields()) {
+               return '#tracker: ' . htmlsc($tracker_form->error);
+       }
+       $fields = $tracker_form->fields;
+       unset($tracker_form);
 
        $from = $to = $hidden = array();
        foreach (array_keys($fields) as $fieldname) {
@@ -123,13 +140,7 @@ function plugin_tracker_action()
        if (PKWK_READONLY) die_message('PKWK_READONLY prohibits editing');
 
        $base  = isset($post['_base'])  ? $post['_base']  : '';
-       $refer = isset($post['_refer']) ? $post['_refer'] : $base;
-       if (! is_pagename($refer)) {
-               return array(
-                       'msg'  => 'Cannot write',
-                       'body' => 'Page name (' . htmlspecialchars($refer) . ') invalid'
-               );
-       }
+       $refer = isset($post['_refer']) ? $post['_refer'] : '';
 
        // $page name to add will be decided here
        $num  = 0;
@@ -146,14 +157,9 @@ function plugin_tracker_action()
                $page = $base . '/' . $real;
        }
 
-       // Loading configuration
-       $config_name = isset($post['_config']) ? $post['_config'] : '';
-       $config = new Config('plugin/tracker/' . $config_name);
-       if (! $config->read()) {
-               return '<p>config file \'' . htmlspecialchars($config_name) . '\' not found.</p>';
-       }
-       $config->config_name = $config_name;
+       $config = isset($post['_config']) ? $post['_config'] : '';
 
+       // TODO: Why here
        // Default
        $_post = array_merge($post, $_FILES);
        $_post['_date'] = $now;
@@ -162,24 +168,39 @@ function plugin_tracker_action()
        $_post['_real'] = $real;
        // $_post['_refer'] = $_post['refer'];
 
+       // TODO: Why here => See BugTrack/662
        // Creating an empty page, before attaching files
        pkwk_touch_file(get_filename($page));
 
        $from = $to = array();
 
+       $tracker_form = & new Tracker_form();
+       if (! $tracker_form->init($base, $refer, $config)) {
+               return array(
+                       'msg'  => 'Cannot write',
+                       'body' => htmlsc($tracker_form->error)
+               );
+       }
+
        // Load $template
-       $template_page = $config->page . '/page';
+       $template_page = $tracker_form->config->page . '/' . PLUGIN_TRACKER_DEFAULT_PAGE;
        $template = plugin_tracker_get_source($template_page);
        if ($template === FALSE || empty($template)) {
                return array(
                        'msg'  => 'Cannot write',
-                       'body' => 'Page template (' . htmlspecialchars($template_page) . ') not exists or seems empty'
+                       'body' => 'Page template (' . htmlsc($template_page) . ') not found'
+               );
+       }
+
+       if (! $tracker_form->initFields(plugin_tracker_field_pickup(implode('', $template)))) {
+               return array(
+                       'msg'  => 'Cannot write',
+                       'body' => htmlsc($tracker_form->error)
                );
        }
+       $fields = $tracker_form->fields;
+       unset($tracker_form);
 
-       $form = & new Tracker_form($base, $refer, $config);
-       $form->initFields(plugin_tracker_field_pickup(implode('', $template)));
-       $fields = & $form->fields;      // unset()
        foreach (array_keys($fields) as $field) {
                $from[] = '[' . $field . ']';
                $to[]   = isset($_post[$field]) ? $fields[$field]->format_value($_post[$field]) : '';
@@ -228,31 +249,74 @@ function plugin_tracker_action()
 // Data set of XHTML form or something
 class Tracker_form
 {
-       var $id;        // Unique id per instance
-
        var $base;
        var $refer;
-       var $config;
+       var $config_name;
+
+       var $config;    // class Config
 
        var $raw_fields;
        var $fields = array();
 
        var $error  = '';       // Error message
 
-       function Tracker_form($base, $refer, & $config)
+       function init($base, $refer = '', $config = NULL, $relative = '')
        {
-               static $id = 0;
-               $this->id = ++$id;
+               $base     = trim($base);
+               $refer    = trim($refer);
+               $relative = trim($relative);
+
+               if ($refer  == '') $refer  = $base;
+               if ($base   == '') $base   = $refer;    // Compat
 
-               $this->base   = $base;
-               $this->refer  = $refer;
-               $this->config = & $config;
+               if ($base  == '') {
+                       $this->error = 'Base not specified';
+                       return FALSE;
+               } else if (! is_pagename($refer)) {
+                       $this->error = 'Invalid page name: ' . $refer;
+                       return FALSE;
+               }
+
+               $absolute = get_fullname($relative, $base);
+               if (is_pagename($absolute)) $base = $absolute;
+
+               $this->base  = $base;
+               $this->refer = $refer;
+
+               if ($config !== NULL && ! $this->loadConfig($config)) {
+                       return FALSE;
+               }
+
+               return TRUE;
+       }
+
+       function loadConfig($config = '')
+       {
+               if (isset($this->config)) return TRUE;
+
+               $config = trim($config);
+               if ($config == '') $config = PLUGIN_TRACKER_DEFAULT_CONFIG;
+
+               $obj_config  = new Config('plugin/tracker/' . $config);
+
+               if ($obj_config->read()) {
+                       $this->config      = $obj_config;
+                       $this->config_name = $config;
+                       return TRUE;
+               } else {
+                       $this->error = "Config not found: " . $obj_config->page;
+                       return FALSE;
+               }
        }
 
        // Init $this->raw_fields and $this->fields
-       // TODO: Using func_get_args() to shrink the code?
        function initFields($requests = NULL)
        {
+               // No argument
+               if (func_num_args() == 0 && $requests === NULL) {
+                       return $this->initFields(NULL);
+               }
+
                if (! isset($this->raw_fields)) {
                        $raw_fields = array();
                        // From config
@@ -289,22 +353,16 @@ class Tracker_form
                        $raw_fields = & $this->raw_fields;
                }
 
-               if ($requests === NULL) {
-                       // (The rest of) All, defined order
-                       foreach ($raw_fields as $fieldname => $field) {
-                               $err = $this->addField(
-                                       $fieldname,
-                                       $field['display'],
-                                       $field['type'],
-                                       $field['options'],
-                                       $field['default']
-                               );
-                               if ($err === FALSE) return FALSE;
+               foreach(func_get_args() as $requests) {
+                       if (empty($raw_fields)) return TRUE;
+
+                       if (! is_array($requests)) {
+                               if ($requests === NULL) {
+                                       $requests = array_keys($raw_fields);    // (The rest of) All, defined order
+                               } else {
+                                       $requests = array($requests);   // Just one
+                               }
                        }
-                       $raw_fields = array();
-               } else {
-                       // Part of, specific order
-                       if (! is_array($requests)) $requests = array($requests);
                        foreach ($requests as $fieldname) {
                                if (! isset($raw_fields[$fieldname])) continue;
                                $field = $raw_fields[$fieldname];
@@ -316,7 +374,7 @@ class Tracker_form
                                        $field['default']
                                );
                                unset($raw_fields[$fieldname]);
-                               if ($err === FALSE) return FALSE;
+                               if (! $err) return FALSE;
                        }
                }
 
@@ -326,26 +384,22 @@ class Tracker_form
        function initHiddenFields()
        {
                // Make sure to init $this->raw_fields
-               $this->initFields(array());
+               if (! $this->initFields(array())) return FALSE;
 
                $fields = array();
                foreach ($this->raw_fields as $fieldname => $field) {
-                       if ($field['type'] == 'hidden') {
+                       if (isset($field['type']) && $field['type'] == 'hidden') {
                                $fields[] = $fieldname;
                        }
                }
 
-               $this->initFields($fields);
+               return $this->initFields($fields);
        }
 
-       // Called from InitFields()
+       // Add $this->fields
        function addField($fieldname, $displayname, $type = 'text', $options = '20', $default = '')
        {
-               // Not Init
-               if (isset($this->fields[$fieldname])) {
-                       $this->error = "No such field: " . $fieldname;
-                       return FALSE;
-               }
+               if (isset($this->fields[$fieldname])) return TRUE;      // Already
 
                $class = 'Tracker_field_' . $type;
                if (! class_exists($class)) {
@@ -373,7 +427,6 @@ class Tracker_form
 class Tracker_field
 {
        var $id;        // Unique id per instance, and per class(extended-class)
-
        var $form;      // Parent (class Tracker_form)
 
        var $name;
@@ -419,14 +472,22 @@ class Tracker_field
                return $value;
        }
 
+       // Get $this->formats[$key] for format_value()), or
+       // Get $this->styles[$key]  for get_style()
+       // from cell contents
+       function get_key($value)
+       {
+               return $value;
+       }
+
        // Format table cell data before output the wiki text
-       function format_cell($str)
+       function format_cell($value)
        {
-               return $str;
+               return $value;
        }
 
        // Format-string for sprintf() before output the wiki text
-       function get_style()
+       function get_style($value)
        {
                return '%s';
        }
@@ -438,9 +499,9 @@ class Tracker_field_text extends Tracker_field
 
        function get_tag()
        {
-               $s_name  = htmlspecialchars($this->name);
-               $s_size  = isset($this->options[0]) ? htmlspecialchars($this->options[0]) : '';
-               $s_value = htmlspecialchars($this->default_value);
+               $s_name  = htmlsc($this->name);
+               $s_size  = isset($this->options[0]) ? htmlsc($this->options[0]) : '';
+               $s_value = htmlsc($this->default_value);
 
                return '<input type="text"' .
                                ' name="'  . $s_name  . '"' .
@@ -487,7 +548,8 @@ class Tracker_field_real extends Tracker_field_text
 
        function format_cell($value)
        {
-               // basename(): Rough but work with this(PLUGIN_TRACKER_LIST_EXCLUDE_PATTERN prohibits '/') situation
+               // basename(): Rough but work with this
+               // (PLUGIN_TRACKER_LIST_EXCLUDE_PATTERN prohibits '/') situation
                return basename($value);
        }
 }
@@ -497,10 +559,10 @@ class Tracker_field_title extends Tracker_field_text
 {
        var $sort_type = PLUGIN_TRACKER_SORT_TYPE_STRING;
 
-       function format_cell($str)
+       function format_cell($value)
        {
-               make_heading($str);
-               return $str;
+               make_heading($value);
+               return $value;
        }
 }
 
@@ -510,10 +572,10 @@ class Tracker_field_textarea extends Tracker_field
 
        function get_tag()
        {
-               $s_name = htmlspecialchars($this->name);
-               $s_cols = isset($this->options[0]) ? htmlspecialchars($this->options[0]) : '';
-               $s_rows = isset($this->options[1]) ? htmlspecialchars($this->options[1]) : '';
-               $s_default = htmlspecialchars($this->default_value);
+               $s_name    = htmlsc($this->name);
+               $s_cols    = isset($this->options[0]) ? htmlsc($this->options[0]) : '';
+               $s_rows    = isset($this->options[1]) ? htmlsc($this->options[1]) : '';
+               $s_default = htmlsc($this->default_value);
 
                return '<textarea' .
                                ' name="' . $s_name . '"' .
@@ -523,18 +585,18 @@ class Tracker_field_textarea extends Tracker_field
                        '</textarea>';
        }
 
-       function format_cell($str)
+       function format_cell($value)
        {
                // Cut too long ones
                // TODO: Why store all of them to the memory?
                if (isset($this->options[2])) {
                        $limit = max(0, $this->options[2]);
-                       $len = mb_strlen($str);
+                       $len = mb_strlen($value);
                        if ($len > ($limit + 3)) {      // 3 = mb_strlen('...')
-                               $str = mb_substr($str, 0, $limit) . '...';
+                               $value = mb_substr($value, 0, $limit) . '...';
                        }
                }
-               return $str;
+               return $value;
        }
 }
 
@@ -550,6 +612,7 @@ class Tracker_field_format extends Tracker_field
        function Tracker_field_format(& $tracker_form, $field)
        {
                parent::Tracker_field($tracker_form, $field);
+
                foreach ($this->form->config->get($this->name) as $option) {
                        list($key, $style, $format) = array_pad(array_map('trim', $option), 3, '');
                        if ($style  != '') $this->styles[$key]  = $style;
@@ -557,32 +620,32 @@ class Tracker_field_format extends Tracker_field
                }
        }
 
-       function _get_key($str)
+       function get_key($value)
        {
-               return ($str == '') ? 'IS NULL' : 'IS NOT NULL';
+               return ($value == '') ? 'IS NULL' : 'IS NOT NULL';
        }
 
        function get_tag()
        {
-               $s_name = htmlspecialchars($this->name);
-               $s_size = isset($this->options[0]) ? htmlspecialchars($this->options[0]) : '';
+               $s_name = htmlsc($this->name);
+               $s_size = isset($this->options[0]) ? htmlsc($this->options[0]) : '';
 
                return '<input type="text" name="' . $s_name . '" size="' . $s_size . '" />';
        }
 
-       function format_value($str)
+       function format_value($value)
        {
-               if (is_array($str)) {
-                       return join(', ', array_map(array($this, 'format_value'), $str));
+               if (is_array($value)) {
+                       return join(', ', array_map(array($this, 'format_value'), $value));
                }
 
-               $key = $this->_get_key($str);
-               return isset($this->formats[$key]) ? str_replace('%s', $str, $this->formats[$key]) : $str;
+               $key = $this->get_key($value);
+               return isset($this->formats[$key]) ? str_replace('%s', $value, $this->formats[$key]) : $value;
        }
 
-       function get_style($str)
+       function get_style($value)
        {
-               $key = $this->_get_key($str);
+               $key = $this->get_key($value);
                return isset($this->styles[$key]) ? $this->styles[$key] : '%s';
        }
 }
@@ -593,8 +656,8 @@ class Tracker_field_file extends Tracker_field_format
 
        function get_tag()
        {
-               $s_name = htmlspecialchars($this->name);
-               $s_size = isset($this->options[0]) ? htmlspecialchars($this->options[0]) : '';
+               $s_name = htmlsc($this->name);
+               $s_size = isset($this->options[0]) ? htmlsc($this->options[0]) : '';
 
                return '<input type="file" name="' . $s_name . '" size="' . $s_size . '" />';
        }
@@ -602,7 +665,6 @@ class Tracker_field_file extends Tracker_field_format
        function format_value()
        {
                if (isset($_FILES[$this->name])) {
-
                        require_once(PLUGIN_DIR . 'attach.inc.php');
 
                        $base = $this->form->base;
@@ -628,11 +690,11 @@ class Tracker_field_radio extends Tracker_field_format
                $retval = '';
 
                $id = 0;
-               $s_name = htmlspecialchars($this->name);
+               $s_name = htmlsc($this->name);
                foreach ($this->form->config->get($this->name) as $option) {
                        ++$id;
                        $s_id = '_p_tracker_' . $s_name . '_' . $this->id . '_' . $id;
-                       $s_option = htmlspecialchars($option[0]);
+                       $s_option = htmlsc($option[0]);
                        $checked  = trim($option[0]) === trim($this->default_value) ? ' checked="checked"' : '';
 
                        $retval .= '<input type="radio"' .
@@ -658,6 +720,12 @@ class Tracker_field_radio extends Tracker_field_format
 
                return isset($options[$name][$value]) ? $options[$name][$value] : $value;
        }
+
+       // Revert(re-overload) Tracker_field_format's specific code
+       function get_key($value)
+       {
+               return $value;
+       }
 }
 
 class Tracker_field_select extends Tracker_field_radio
@@ -675,9 +743,9 @@ class Tracker_field_select extends Tracker_field_radio
 
                $retval = array();
 
-               $s_name = htmlspecialchars($this->name);
+               $s_name = htmlsc($this->name);
                $s_size = (isset($this->options[0]) && is_numeric($this->options[0])) ?
-                       ' size="' . htmlspecialchars($this->options[0]) . '"' : '';
+                       ' size="' . htmlsc($this->options[0]) . '"' : '';
                $s_multiple = (isset($this->options[1]) && strtolower($this->options[1]) == 'multiple') ?
                        ' multiple="multiple"' : '';
                $retval[] = '<select name="' . $s_name . '[]"' . $s_size . $s_multiple . '>';
@@ -686,7 +754,7 @@ class Tracker_field_select extends Tracker_field_radio
 
                foreach ($this->form->config->get($this->name) as $option) {
                        $option   = reset($option);
-                       $s_option = htmlspecialchars($option);
+                       $s_option = htmlsc($option);
                        $selected = isset($defaults[trim($option)]) ? ' selected="selected"' : '';
                        $retval[] = ' <option value="' . $s_option . '"' . $selected . '>' . $s_option . '</option>';
                }
@@ -703,22 +771,23 @@ class Tracker_field_checkbox extends Tracker_field_radio
 
        function get_tag()
        {
-               $retval = '';
+               $config   = $this->form->config;
 
-               $id = 0;
-               $s_name   = htmlspecialchars($this->name);
+               $s_name   = htmlsc($this->name);
+               $s_fid    = htmlsc($this->id);
                $defaults = array_flip(preg_split('/\s*,\s*/', $this->default_value, -1, PREG_SPLIT_NO_EMPTY));
-               foreach ($this->form->config->get($this->name) as $option) {
+
+               $id     = 0;
+               $retval = '';
+               foreach ($config->get($this->name) as $option) {
                        ++$id;
-                       $s_id     = '_p_tracker_' . $s_name . '_' . $this->id . '_' . $id;
-                       $s_option = htmlspecialchars($option[0]);
+                       $s_id     = '_p_tracker_' . $s_name . '_' . $s_fid . '_' . $id;
+                       $s_option = htmlsc($option[0]);
                        $checked  = isset($defaults[trim($option[0])]) ? ' checked="checked"' : '';
 
                        $retval .= '<input type="checkbox"' .
-                               ' name="' . $s_name . '[]"' .
-                               ' id="' . $s_id . '"' .
-                               ' value="' . $s_option . '"' .
-                               $checked . ' />' .
+                               ' name="' . $s_name . '[]" id="' . $s_id . '"' .
+                               ' value="' . $s_option . '"' . $checked . ' />' .
                                '<label for="' . $s_id . '">' . $s_option . '</label>' . "\n";
                }
 
@@ -732,9 +801,12 @@ class Tracker_field_hidden extends Tracker_field_radio
 
        function get_tag()
        {
+               $s_name    = htmlsc($this->name);
+               $s_default = htmlsc($this->default_value);
+
                return '<input type="hidden"' .
-                       ' name="'  . htmlspecialchars($this->name)          . '"' .
-                       ' value="' . htmlspecialchars($this->default_value) . '" />' . "\n";
+                       ' name="'  . $s_name    . '"' .
+                       ' value="' . $s_default . '" />' . "\n";
        }
 }
 
@@ -744,10 +816,10 @@ class Tracker_field_submit extends Tracker_field
        {
                $form = $this->form;
 
-               $s_title  = htmlspecialchars($this->title);
-               $s_base   = htmlspecialchars($form->base);
-               $s_refer  = htmlspecialchars($form->refer);
-               $s_config = htmlspecialchars($form->config->config_name);
+               $s_title  = htmlsc($this->title);
+               $s_base   = htmlsc($form->base);
+               $s_refer  = htmlsc($form->refer);
+               $s_config = htmlsc($form->config_name);
 
                return <<<EOD
 <input type="submit" value="$s_title" />
@@ -773,9 +845,9 @@ class Tracker_field_past extends Tracker_field
 {
        var $sort_type = PLUGIN_TRACKER_SORT_TYPE_NUMERIC;
 
-       function get_value($value)
+       function get_value($timestamp)
        {
-               return UTIME - $value;
+               return UTIME - $timestamp;
        }
 
        function format_cell($timestamp)
@@ -791,96 +863,75 @@ function plugin_tracker_list_convert()
 {
        global $vars;
 
-       $base = $refer = isset($vars['page']) ? $vars['page'] : '';
-       $config_name = PLUGIN_TRACKER_DEFAULT_CONFIG;
-       $list        = PLUGIN_TRACKER_DEFAULT_LIST;
-       $limit       = PLUGIN_TRACKER_DEFAULT_LIMIT;
-       $order       = PLUGIN_TRACKER_DEFAULT_ORDER;
-
        $args = func_get_args();
        $argc = count($args);
        if ($argc > 4) {
                return PLUGIN_TRACKER_LIST_USAGE . '<br />';
        }
+
+       $base   = isset($vars['page']) ? $vars['page'] : '';
+       $refer  = '';
+       $rel    = '';
+       $config = '';
+       $order  = '';
+       $list   = '';
+       $limit  = NULL;
        switch ($argc) {
        case 4: $limit = $args[3];      /*FALLTHROUGH*/
        case 3: $order = $args[2];      /*FALLTHROUGH*/
-       case 2:
-               $arg = get_fullname($args[1], $base);
-               if (is_pagename($arg)) $base = $arg;
-               /*FALLTHROUGH*/
+       case 2: $rel   = $args[1];      /*FALLTHROUGH*/
        case 1:
-               // Config/list
+               // Set "$config/$list"
                if ($args[0] != '') {
                        $arg = explode('/', $args[0], 2);
-                       if ($arg[0] != '' ) $config_name = $arg[0];
-                       if (isset($arg[1])) $list        = $arg[1];
+                       if ($arg[0] != '' ) $config = $arg[0];
+                       if (isset($arg[1])) $list   = $arg[1];
                }
        }
+
        unset($args, $argc, $arg);
 
-       return plugin_tracker_list_render($base, $refer, $config_name, $list, $order, $limit);
+       return plugin_tracker_list_render($base, $refer, $rel, $config, $order, $list, $limit);
 }
 
 function plugin_tracker_list_action()
 {
-       global $get, $vars;
-
-       $base = isset($get['base']) ? $get['base'] : '';        // Base directory to load
-
-       if (isset($get['refer'])) {
-               $refer = $get['refer']; // Where to #tracker_list
-               if ($base == '') $base = $refer;
-       } else {
-               $refer = $base;
-       }
+       global $get;
 
+       $base   = isset($get['base'])   ? $get['base']   : '';
+       $refer  = isset($get['refer'])  ? $get['refer']  : '';
+       $rel    = '';
        $config = isset($get['config']) ? $get['config'] : '';
-       $list   = isset($get['list'])   ? $get['list']   : 'list';
-       $order  = isset($get['order'])  ? $get['order']  : PLUGIN_TRACKER_DEFAULT_ORDER;
-       $limit  = isset($get['limit'])  ? $get['limit']  : 0;
+       $order  = isset($get['order'])  ? $get['order']  : '';
+       $list   = isset($get['list'])   ? $get['list']   : '';
+       $limit  = isset($get['limit'])  ? $get['limit']  : NULL;
 
        $s_refer = make_pagelink($refer);
+
        return array(
                'msg' => plugin_tracker_message('msg_list'),
-               'body'=> str_replace('$1', $s_refer, plugin_tracker_message('msg_back')) .
-                       plugin_tracker_list_render($base, $refer, $config, $list, $order, $limit)
+               'body'=>
+                       str_replace('$1', $s_refer, plugin_tracker_message('msg_back')) .
+                       plugin_tracker_list_render($base, $refer, $rel, $config, $order, $list, $limit)
        );
 }
 
-function plugin_tracker_list_render($base, $refer, $config_name, $list, $order_commands = '', $limit = 0)
+function plugin_tracker_list_render($base, $refer, $rel = '', $config = '', $order = '', $list = '', $limit = NULL)
 {
-       $base  = trim($base);
-       if ($base == '') return '#tracker_list: Base not specified' . '<br />';
+       $tracker_list = & new Tracker_list();
 
-       $refer = trim($refer);
-       if (! is_page($refer)) {
-               return '#tracker_list: Refer page not found: ' . htmlspecialchars($refer) . '<br />';
+       if (! $tracker_list->init($base, $refer, $config, $rel)  ||
+               ! $tracker_list->setSortOrder($order)) {
+               return '#tracker_list: ' . htmlsc($tracker_list->error) . '<br />';
        }
 
-       $config_name = trim($config_name);
-       if ($config_name == '') $config_name = PLUGIN_TRACKER_DEFAULT_CONFIG;
-
-       $list  = trim($list);
-       if (! is_numeric($limit)) return PLUGIN_TRACKER_LIST_USAGE . '<br />';
-       $limit = intval($limit);
-
-       $config = new Config('plugin/tracker/' . $config_name);
-       if (! $config->read()) {
-               return '#tracker_list: Config not found: ' . htmlspecialchars($config_name) . '<br />';
-       }
-       $config->config_name = $config_name;
-       if (! is_page($config->page . '/' . $list)) {
-               return '#tracker_list: List not found: ' . make_pagelink($config->page . '/' . $list) . '<br />';
+       if (! is_page($tracker_list->form->refer)) {
+               return '#tracker_list: Refer page not found: ' . htmlsc($refer) . '<br />';
        }
 
-       $tracker_list = & new Tracker_list($base, $refer, $config);
-       if ($tracker_list->setSortOrder($order_commands) === FALSE) {
-               return '#tracker_list: ' . htmlspecialchars($tracker_list->error) . '<br />';
-       }
        $result = $tracker_list->toString($list, $limit);
        if ($result === FALSE) {
-               return '#tracker_list: ' . htmlspecialchars($tracker_list->error) . '<br />';
+               return '#tracker_list: ' . htmlsc($tracker_list->error) . '<br />';
        }
        unset($tracker_list);
 
@@ -893,7 +944,7 @@ class Tracker_list
        var $form;      // class Tracker_form
 
        var $rows   = array();
-       var $orders = array();
+       var $orders;
        var $error  = '';       // Error message
 
        // _generate_regex()
@@ -908,37 +959,18 @@ class Tracker_list
        var $_row;
        var $_the_first_character_of_the_line;
 
-       function Tracker_list($base, $refer, & $config)
+       function init($base, $refer, $config = NULL, $relative = '')
        {
-               $this->form = & new Tracker_form($base, $refer, $config);
-       }
-
-       // Adding $this->rows
-       // Add multiple pages at a time
-       function loadRows()
-       {
-               $base  = $this->form->base . '/';
-               $len   = strlen($base);
-               $regex = '#^' . preg_quote($base, '#') . '#';
-
-               foreach (preg_grep($regex, array_values(get_existpages())) as $pagename) {
-                       if (preg_match(PLUGIN_TRACKER_LIST_EXCLUDE_PATTERN, substr($pagename, $len))) {
-                               continue;
-                       }
-                       if ($this->addRow($pagename) === FALSE) return FALSE;
-               }
-               if (empty($this->rows)) {
-                       $this->error = 'Pages not found under: ' . $base;
-                       return FALSE;
-               }
-
-               return TRUE;
+               $this->form = & new Tracker_form();
+               return $this->form->init($base, $refer, $config, $relative);
        }
 
-       // addRow(): Generate regex to load a page
+       // Generate/Regenerate regex to load one page
        function _generate_regex()
        {
-               $template_page = $this->form->config->page . '/page';
+               if (isset($this->pattern) && isset($this->pattern_fields)) return TRUE;
+
+               $template_page = $this->form->config->page . '/' . 'page';
                $fields        = $this->form->fields;
                
                $pattern        = array();
@@ -970,15 +1002,41 @@ class Tracker_list
                                $pattern[] = '.*?';     // Just ignore pseudo fields etc
                        }
                }
+
                $this->pattern        = '/' . implode('', $pattern) . '/sS';
                $this->pattern_fields = $pattern_fields;
 
                return TRUE;
        }
 
+       // Adding $this->rows
+       // Add multiple pages at a time
+       function loadRows()
+       {
+               $base  = $this->form->base . '/';
+               $len   = strlen($base);
+               $regex = '#^' . preg_quote($base, '#') . '#';
+
+               foreach (preg_grep($regex, array_values(get_existpages())) as $pagename) {
+                       if (preg_match(PLUGIN_TRACKER_LIST_EXCLUDE_PATTERN, substr($pagename, $len))) {
+                               continue;
+                       }
+                       if ($this->addRow($pagename) === FALSE) return FALSE;
+               }
+               if (empty($this->rows)) {
+                       $this->error = 'Pages not found under: ' . $base;
+                       return FALSE;
+               }
+
+               return TRUE;
+       }
+
        // Add one pages
        function addRow($pagename, $rescan = FALSE)
        {
+               // Generate/Regenerate regex if needed
+               if ($this->_generate_regex() === FALSE) return FALSE;
+
                if (isset($this->_added[$pagename])) return TRUE;
                $this->_added[$pagename] = TRUE;
 
@@ -1025,6 +1083,7 @@ class Tracker_list
        function _order_commands2orders($order_commands = '')
        {
                $order_commands = trim($order_commands);
+               if ($order_commands == '') $order_commands = PLUGIN_TRACKER_DEFAULT_ORDER;
                if ($order_commands == '') return array();
 
                $orders = array();
@@ -1061,11 +1120,12 @@ class Tracker_list
        {
                $orders = $this->_order_commands2orders($order_commands);
                if ($orders === FALSE) {
-                       $this->orders = array();
+                       unset($this->orders);
                        return FALSE;
+               } else {
+                       $this->orders = $orders;
+                       return TRUE;
                }
-               $this->orders = $orders;
-               return TRUE;
        }
 
        // sortRows(): Internal sort type => PHP sort define
@@ -1097,6 +1157,11 @@ class Tracker_list
        // Sort $this->rows by $this->orders
        function sortRows()
        {
+               if (! isset($this->orders)) {
+                       $this->error = "Sort order seems not set";
+                       return FALSE;
+               }
+
                $fields = $this->form->fields;
                $orders = $this->orders;
                $types  = array();
@@ -1191,14 +1256,14 @@ class Tracker_list
        // toString(): Called within preg_replace_callback()
        function _replace_title($matches = array())
        {
-               $form        = $this->form;
-               $base        = $form->base;
-               $refer       = $form->refer;
-               $fields      = $form->fields;
-               $config_name = $form->config->config_name;
+               $form   = $this->form;
+               $base   = $form->base;
+               $refer  = $form->refer;
+               $fields = $form->fields;
+               $config = $form->config_name;
 
-               $orders      = $this->orders;
-               $list        = $this->_list;
+               $orders = $this->orders;
+               $list   = $this->_list;
 
                $fieldname = isset($matches[1]) ? $matches[1] : '';
                if (! isset($fields[$fieldname])) {
@@ -1239,14 +1304,10 @@ class Tracker_list
                }
 
                $script = get_script_uri();
-               $r_base   = ($refer != $base) ?
-                       '&base='  . rawurlencode($base) : '';
-               $r_config = ($config_name != PLUGIN_TRACKER_DEFAULT_CONFIG) ?
-                       '&config=' . rawurlencode($config_name) : '';
-               $r_list   = ($list != PLUGIN_TRACKER_DEFAULT_LIST) ?
-                       '&list=' . rawurlencode($list) : '';
-               $r_order  = ! empty($_orders) ?
-                       '&order=' . rawurlencode(join(';', $_orders)) : '';
+               $r_base   = ($refer  != $base) ? '&base='  . rawurlencode($base) : '';
+               $r_config = ($config != PLUGIN_TRACKER_DEFAULT_CONFIG) ? '&config=' . rawurlencode($config) : '';
+               $r_list   = ($list   != PLUGIN_TRACKER_DEFAULT_LIST  ) ? '&list='   . rawurlencode($list)   : '';
+               $r_order  = ! empty($_orders) ? '&order=' . rawurlencode(join(';', $_orders)) : '';
 
                return
                         '[[' .
@@ -1275,7 +1336,7 @@ class Tracker_list
                        if (! isset($row[$fieldname])) {
                                // Maybe load miss of the page
                                if (isset($fields[$fieldname])) {
-                                       $str = '[page_err]';    // Exactlly
+                                       $str = '[match_err]';   // Exactlly
                                } else {
                                        $str = isset($matches[0]) ? $matches[0] : '';   // Nothing to do
                                }
@@ -1285,6 +1346,7 @@ class Tracker_list
                                        $str = $fields[$fieldname]->format_cell($str);
                                }
                        }
+                       $str = plugin_tracker_escape($str, $tfc);
                }
 
                if (isset($fields[$stylename]) && isset($row[$stylename])) {
@@ -1292,16 +1354,25 @@ class Tracker_list
                        $str    = sprintf($_style, $str);
                }
 
-               return plugin_tracker_escape($str, $tfc);
+               return $str;
        }
 
        // Output a part of Wiki text
-       function toString($list, $limit = 0)
+       function toString($list = PLUGIN_TRACKER_DEFAULT_LIST, $limit = NULL)
        {
+               $list = trim($list);
+               if ($list == '') $list = PLUGIN_TRACKER_DEFAULT_LIST;
+
+               if ($limit == NULL) $limit = PLUGIN_TRACKER_DEFAULT_LIMIT;
+               if (! is_numeric($limit)) {
+                       $this->error = "Limit seems not numeric: " . $limit;
+                       return FALSE;
+               }
+       
                $form   = & $this->form;
 
                $this->_list = $list;   // For _replace_title() only
-               $list   = $form->config->page . '/' . $list;
+               $list = $form->config->page . '/' . $list;
 
                $source = array();
                $regex  = '/\[([^\[\]]+)\]/';
@@ -1309,28 +1380,21 @@ class Tracker_list
                // Loading template
                $template = plugin_tracker_get_source($list, TRUE);
                if ($template === FALSE || empty($template)) {
-                       $this->error = 'Page not found or seems empty: ' . $list;
+                       $this->error = 'List not found: ' . $list;
                        return FALSE;
                }
 
                // Try to create $form->fields just you need
-               if ($form->initFields('_real') === FALSE ||
-                   $form->initFields(plugin_tracker_field_pickup($template)) === FALSE ||
-                   $form->initFields(array_keys($this->orders)) === FALSE) {
+               if ($form->initFields('_real', plugin_tracker_field_pickup($template),
+                   array_keys($this->orders)) === FALSE) {
                        $this->error = $form->error;
                        return FALSE;
                }
 
-               // TODO: Check isset($this->rows) or something
-               // Generate regex for $form->fields
-               if ($this->_generate_regex() === FALSE) return FALSE;
-
-               // TODO: Check isset($this->rows) or something
                // Load and sort $this->rows
                if ($this->loadRows() === FALSE || $this->sortRows() === FALSE) return FALSE;
                $rows = $this->rows;
 
-
                // toString()
                $count = count($this->rows);
                $limit = intval($limit);