<?php
// PukiWiki - Yet another WikiWikiWeb clone
-// $Id: tracker.inc.php,v 1.40 2007/09/02 14:43: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)
-// #tracker_list: Excluding pattern
+
+// Tracker_list: Excluding pattern
define('PLUGIN_TRACKER_LIST_EXCLUDE_PATTERN','#^SubMenu$|/#'); // 'SubMenu' and using '/'
//define('PLUGIN_TRACKER_LIST_EXCLUDE_PATTERN','#(?!)#'); // Nothing excluded
-// #tracker_list: 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_NATURAL', 6);
+if (! defined('SORT_NATURAL')) define('SORT_NATURAL', PLUGIN_TRACKER_SORT_TYPE_NATURAL);
+
+// Sort order
+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()
{
- global $script, $vars;
+ global $vars;
if (PKWK_READONLY) return ''; // Show nothing
- $base = $refer = $vars['page'];
-
- $config_name = 'default';
- $form = 'form';
- $options = array();
- if (func_num_args()) {
- $args = func_get_args();
- switch (count($args))
- {
- case 3:
- $options = array_splice($args, 2);
- case 2:
- $args[1] = get_fullname($args[1], $base);
- $base = is_pagename($args[1]) ? $args[1] : $base;
- case 1:
- $config_name = ($args[0] != '') ? $args[0] : $config_name;
- list($config_name, $form) = array_pad(explode('/', $config_name, 2), 2, $form);
+ $args = func_get_args();
+ $argc = count($args);
+ if ($argc > 2) return PLUGIN_TRACKER_USAGE . '<br />';
+
+ $base = isset($vars['page']) ? $vars['page'] : '';
+ $refer = '';
+ $config = '';
+ $form = '';
+ $rel = '';
+ switch ($argc) {
+ case 2:
+ $rel = $args[1];
+ /*FALLTHROUGH*/
+ case 1:
+ // Set "$config/$form"
+ if ($args[0] != '') {
+ $arg = explode('/', trim($args[0]), 2);
+ 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 '<p>config file \'' . htmlspecialchars($config_name) . '\' not found.</p>';
+ $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;
-
- $fields = plugin_tracker_get_fields($base, $refer, $config);
-
- $form = $config->page . '/' . $form;
- if (! is_page($form)) {
- return '<p>config file \'' . make_pagelink($form) . '\' not found.</p>';
+ // 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 not found: ' . $form . '<br />';
}
- $retval = convert_html(plugin_tracker_get_source($form));
- $hiddens = '';
-
- foreach (array_keys($fields) as $name) {
- $replace = $fields[$name]->get_tag();
- if (is_a($fields[$name], 'Tracker_field_hidden')) {
- $hiddens .= $replace;
- $replace = '';
+ 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) {
+ $from[] = '[' . $fieldname . ']';
+ $_to = $fields[$fieldname]->get_tag();
+ if (is_a($fields[$fieldname], 'Tracker_field_hidden')) {
+ $to[] = '';
+ $hidden[] = $_to;
+ } else {
+ $to[] = $_to;
}
- $retval = str_replace('[' . $name . ']', $replace, $retval);
+ unset($fields[$fieldname]);
}
+ $script = get_script_uri();
+ $template = str_replace($from, $to, convert_html($template));
+ $hidden = implode('<br />' . "\n", $hidden);
return <<<EOD
<form enctype="multipart/form-data" action="$script" method="post">
<div>
-$retval
-$hiddens
+$template
+$hidden
</div>
</form>
EOD;
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) . ') is not valid.'
- );
- }
+ $base = isset($post['_base']) ? $post['_base'] : '';
+ $refer = isset($post['_refer']) ? $post['_refer'] : '';
// $page name to add will be decided here
- $name = isset($post['_name']) ? $post['_name'] : '';
$num = 0;
+ $name = isset($post['_name']) ? $post['_name'] : '';
if (isset($post['_page'])) {
- $page = $real = $post['_page'];
+ $real = $page = $post['_page'];
} else {
$real = is_pagename($name) ? $name : ++$num;
$page = get_fullname('./' . $real, $base);
$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;
- $template_page = $config->page . '/page';
- if (! is_page($template_page)) {
- return array(
- 'msg' => 'cannot write',
- 'body' => 'page template (' . htmlspecialchars($template_page) . ') is not exist.'
- );
- }
+ $config = isset($post['_config']) ? $post['_config'] : '';
+ // TODO: Why here
// Default
$_post = array_merge($post, $_FILES);
$_post['_date'] = $now;
$_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));
- // Load $fields
- $fields = plugin_tracker_get_fields($page, $refer, $config);
$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 = $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 (' . 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);
+
foreach (array_keys($fields) as $field) {
$from[] = '[' . $field . ']';
$to[] = isset($_post[$field]) ? $fields[$field]->format_value($_post[$field]) : '';
+ unset($fields[$field]);
}
- unset($fields);
- // Load $template
- $template = plugin_tracker_get_source($template_page);
+ // Repalace every [$field]s (found inside $template) to real values
+ $subject = $escape = array();
+ foreach (array_keys($template) as $linenum) {
+ if (trim($template[$linenum]) == '') continue;
- // Repalace every [$field]s to real values in the $template
- $replace = $replace_e = array();
- foreach (array_keys($template) as $num) {
- if (trim($template[$num]) == '') continue;
- $letter = $template[$num]{0};
+ // Escape some TextFormattingRules
+ $letter = $template[$linenum][0];
if ($letter == '|' || $letter == ':') {
- // Escape for some TextFormattingRules: <table> and <dr>
- $replace_e[$num] = $template[$num];
+ $escape['|'][$linenum] = $template[$linenum];
+ } else if ($letter == ',') {
+ $escape[','][$linenum] = $template[$linenum];
} else {
- $replace[$num] = $template[$num];
+ // TODO: Escape "\n" except multiline-allowed fields
+ $subject[$linenum] = $template[$linenum];
}
}
- foreach (str_replace($from, $to, $replace ) as $num => $line) {
- $template[$num] = $line;
+ foreach (str_replace($from, $to, $subject) as $linenum => $line) {
+ $template[$linenum] = $line;
}
- // Escape for some TextFormattingRules: <table> and <dr>
- if ($replace_e) {
- $to_e = array();
- foreach($to as $value) {
- if (strpos($value, '|') !== FALSE) {
- // Escape for some TextFormattingRules: <table> and <dr>
- $to_e[] = str_replace('|', '|', $value);
- } else{
- $to_e[] = $value;
+ if ($escape) {
+ // Escape for some TextFormattingRules
+ foreach(array_keys($escape) as $hint) {
+ $to_e = plugin_tracker_escape($to, $hint);
+ foreach (str_replace($from, $to_e, $escape[$hint]) as $linenum => $line) {
+ $template[$linenum] = $line;
}
}
- foreach (str_replace($from, $to_e, $replace_e) as $num => $line) {
- $template[$num] = $line;
- }
+ unset($to_e);
}
+ unset($from, $to);
// Write $template, without touch
page_write($page, join('', $template));
exit;
}
-/*
-function plugin_tracker_inline()
+// Data set of XHTML form or something
+class Tracker_form
{
- global $vars;
+ var $base;
+ var $refer;
+ var $config_name;
- if (PKWK_READONLY) return ''; // Show nothing
+ var $config; // class Config
- $args = func_get_args();
- if (count($args) < 3) return FALSE;
+ var $raw_fields;
+ var $fields = array();
+
+ var $error = ''; // Error message
+
+ function init($base, $refer = '', $config = NULL, $relative = '')
+ {
+ $base = trim($base);
+ $refer = trim($refer);
+ $relative = trim($relative);
+
+ if ($refer == '') $refer = $base;
+ if ($base == '') $base = $refer; // Compat
+
+ if ($base == '') {
+ $this->error = 'Base not specified';
+ return FALSE;
+ } else if (! is_pagename($refer)) {
+ $this->error = 'Invalid page name: ' . $refer;
+ return FALSE;
+ }
- $body = array_pop($args);
- list($config_name, $field) = $args;
+ $absolute = get_fullname($relative, $base);
+ if (is_pagename($absolute)) $base = $absolute;
- $config = new Config('plugin/tracker/' . $config_name);
+ $this->base = $base;
+ $this->refer = $refer;
- if (! $config->read()) {
- return 'config file \'' . htmlspecialchars($config_name) . '\' not found.';
+ if ($config !== NULL && ! $this->loadConfig($config)) {
+ return FALSE;
+ }
+
+ return TRUE;
}
- $config->config_name = $config_name;
- $fields = plugin_tracker_get_fields($vars['page'], $vars['page'], $config);
- $fields[$field]->default_value = $body;
+ function loadConfig($config = '')
+ {
+ if (isset($this->config)) return TRUE;
- return $fields[$field]->get_tag();
-}
-*/
+ $config = trim($config);
+ if ($config == '') $config = PLUGIN_TRACKER_DEFAULT_CONFIG;
-// Construct field objects
-function plugin_tracker_get_fields($base, $refer, & $config)
-{
- global $now, $_tracker_messages;
+ $obj_config = new Config('plugin/tracker/' . $config);
- $fields = array();
+ 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;
+ }
+ }
- foreach (
- array(
- // Reserved words
- '_date' =>'text', // Post date
- '_update'=>'date', // Last modified date
- '_past' =>'past', // Elapsed time (passage)
- '_page' =>'page', // Page name
- '_name' =>'text', // Page name specified by poster
- '_real' =>'real', // Page name (Real)
- '_refer' =>'page', // Page name refer from this (Page who has forms)
- '_base' =>'page',
- '_submit'=>'submit'
- ) as $field => $class)
- {
- $class = 'Tracker_field_' . $class;
- $fields[$field] = & new $class(
- array($field, $_tracker_messages['btn' . $field], '', '20', ''),
- $base,
- $refer,
- $config
- );
+ // Init $this->raw_fields and $this->fields
+ 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
+ foreach ($this->config->get('fields') as $field) {
+ $fieldname = isset($field[0]) ? $field[0] : '';
+ $raw_fields[$fieldname] = array(
+ 'display' => isset($field[1]) ? $field[1] : '',
+ 'type' => isset($field[2]) ? $field[2] : '',
+ 'options' => isset($field[3]) ? $field[3] : '',
+ 'default' => isset($field[4]) ? $field[4] : '',
+ );
+ }
+ // From reserved
+ $default = array('options' => '20', 'default' => '');
+ foreach (array(
+ '_date' => 'text', // Post date
+ '_update' => 'date', // Last modified date
+ '_past' => 'past', // Elapsed time (passage)
+ '_page' => 'page', // Page name
+ '_name' => 'text', // Page name specified by poster
+ '_real' => 'real', // Page name (Real)
+ '_refer' => 'page', // Page name refer from this (Page who has forms)
+ '_base' => 'page',
+ '_submit' => 'submit'
+ ) as $fieldname => $type) {
+ if (isset($raw_fields[$fieldname])) continue;
+ $raw_fields[$fieldname] = array(
+ 'display' => plugin_tracker_message('btn' . $fieldname),
+ 'type' => $type,
+ ) + $default;
+ }
+ $this->raw_fields = & $raw_fields;
+ } else {
+ $raw_fields = & $this->raw_fields;
+ }
+
+ 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
+ }
+ }
+ foreach ($requests as $fieldname) {
+ if (! isset($raw_fields[$fieldname])) continue;
+ $field = $raw_fields[$fieldname];
+ $err = $this->addField(
+ $fieldname,
+ $field['display'],
+ $field['type'],
+ $field['options'],
+ $field['default']
+ );
+ unset($raw_fields[$fieldname]);
+ if (! $err) return FALSE;
+ }
+ }
+
+ return TRUE;
}
- foreach ($config->get('fields') as $field) {
- // $field[0]: Field name
- // $field[1]: Field name (for display)
- // $field[2]: Field type
- // $field[3]: Option
- // $field[4]: Default value
- $class = 'Tracker_field_' . $field[2];
+ function initHiddenFields()
+ {
+ // Make sure to init $this->raw_fields
+ if (! $this->initFields(array())) return FALSE;
+
+ $fields = array();
+ foreach ($this->raw_fields as $fieldname => $field) {
+ if (isset($field['type']) && $field['type'] == 'hidden') {
+ $fields[] = $fieldname;
+ }
+ }
+
+ return $this->initFields($fields);
+ }
+
+ // Add $this->fields
+ function addField($fieldname, $displayname, $type = 'text', $options = '20', $default = '')
+ {
+ if (isset($this->fields[$fieldname])) return TRUE; // Already
+
+ $class = 'Tracker_field_' . $type;
if (! class_exists($class)) {
- // Default
- $class = 'Tracker_field_text';
- $field[2] = 'text';
- $field[3] = '20';
+ $this->error = "No such type: " . $type;
+ return FALSE;
}
- $fields[$field[0]] = & new $class($field, $base, $refer, $config);
+
+ $this->fields[$fieldname] = & new $class(
+ $this, // Reference
+ array(
+ $fieldname,
+ $displayname,
+ NULL, // $type
+ $options,
+ $default
+ )
+ );
+
+ return TRUE;
}
- return $fields;
}
-// Field classes
+// TODO: Why a filter sometimes created so many?
+// Field classes within a form
class Tracker_field
{
+ var $id; // Unique id per instance, and per class(extended-class)
+ var $form; // Parent (class Tracker_form)
+
var $name;
var $title;
- var $values;
+ var $options;
var $default_value;
- var $page;
- var $refer;
- var $config;
+
var $data;
- var $sort_type = SORT_REGULAR;
- var $id = 0;
- function Tracker_field($field, $page, $refer, & $config)
+ var $sort_type = PLUGIN_TRACKER_SORT_TYPE_REGULAR;
+
+ function Tracker_field(& $tracker_form, $field)
{
global $post;
static $id = 0;
- $this->id = ++$id;
- $this->name = $field[0];
- $this->title = $field[1];
- $this->values = explode(',', $field[3]);
- $this->default_value = $field[4];
- $this->page = $page;
- $this->refer = $refer;
- $this->config = & $config;
- $this->data = isset($post[$this->name]) ? $post[$this->name] : '';
+ $this->id = ++$id;
+
+ $this->form = & $tracker_form;
+ $this->name = isset($field[0]) ? $field[0] : '';
+ $this->title = isset($field[1]) ? $field[1] : '';
+ $this->options = isset($field[3]) ? explode(',', $field[3]) : array();
+ $this->default_value = isset($field[4]) ? $field[4] : '';
+
+ $this->data = isset($post[$this->name]) ? $post[$this->name] : '';
}
+ // Output a part of XHTML form for the field
function get_tag()
{
+ return '';
}
- function get_style($str)
+ // Format user input before write
+ function format_value($value)
{
- return '%s';
+ return $value;
}
- function format_value($value)
+ // Compare key for Tracker_list->sort()
+ function get_value($value)
{
return $value;
}
- function format_cell($str)
+ // Get $this->formats[$key] for format_value()), or
+ // Get $this->styles[$key] for get_style()
+ // from cell contents
+ function get_key($value)
{
- return $str;
+ return $value;
}
- function get_value($value)
+ // Format table cell data before output the wiki text
+ function format_cell($value)
{
return $value;
}
+
+ // Format-string for sprintf() before output the wiki text
+ function get_style($value)
+ {
+ return '%s';
+ }
}
class Tracker_field_text extends Tracker_field
{
- var $sort_type = SORT_STRING;
+ var $sort_type = PLUGIN_TRACKER_SORT_TYPE_STRING;
function get_tag()
{
+ $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="' . htmlspecialchars($this->name) . '"' .
- ' size="' . htmlspecialchars($this->values[0]) . '"' .
- ' value="' . htmlspecialchars($this->default_value) . '" />';
+ ' name="' . $s_name . '"' .
+ ' size="' . $s_size . '"' .
+ ' value="' . $s_value . '" />';
}
}
+// Special type: Page name with link syntax
class Tracker_field_page extends Tracker_field_text
{
- var $sort_type = SORT_STRING;
+ var $sort_type = PLUGIN_TRACKER_SORT_TYPE_STRING;
+
+ function _format($page)
+ {
+ $page = strip_bracket($page);
+ if (is_pagename($page)) $page = '[[' . $page . ']]';
+ return $page;
+ }
function format_value($value)
{
- $value = strip_bracket($value);
- if (is_pagename($value)) $value = '[[' . $value . ']]';
- return parent::format_value($value);
+ return $this->_format($value);
+ }
+
+ function format_cell($value)
+ {
+ return $this->_format($value);
}
}
+// Special type: Page name minus 'base'
+// e.g.
+// page name: Tracker/sales/100
+// base : Tracker/sales
+// _real : 100
+//
+// NOTE:
+// Don't consider using within ":config/plugin/tracker/*/page".
+// This value comes from _the_page_name_ itself.
class Tracker_field_real extends Tracker_field_text
{
- var $sort_type = SORT_REGULAR;
+ var $sort_type = PLUGIN_TRACKER_SORT_TYPE_NATURAL;
+
+ function format_cell($value)
+ {
+ // basename(): Rough but work with this
+ // (PLUGIN_TRACKER_LIST_EXCLUDE_PATTERN prohibits '/') situation
+ return basename($value);
+ }
}
+// Special type: For headings cleaning
class Tracker_field_title extends Tracker_field_text
{
- var $sort_type = SORT_STRING;
+ 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;
}
}
class Tracker_field_textarea extends Tracker_field
{
- var $sort_type = SORT_STRING;
+ var $sort_type = PLUGIN_TRACKER_SORT_TYPE_STRING;
function get_tag()
{
+ $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="' . htmlspecialchars($this->name) . '"' .
- ' cols="' . htmlspecialchars($this->values[0]) . '"' .
- ' rows="' . htmlspecialchars($this->values[1]) . '">' .
- htmlspecialchars($this->default_value) .
+ ' name="' . $s_name . '"' .
+ ' cols="' . $s_cols . '"' .
+ ' rows="' . $s_rows . '">' .
+ $s_default .
'</textarea>';
}
- function format_cell($str)
+ function format_cell($value)
{
- $str = preg_replace('/[\r\n]+/', '', $str);
- if (! empty($this->values[2]) && strlen($str) > ($this->values[2] + 3)) {
- $str = mb_substr($str, 0, $this->values[2]) . '...';
+ // 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($value);
+ if ($len > ($limit + 3)) { // 3 = mb_strlen('...')
+ $value = mb_substr($value, 0, $limit) . '...';
+ }
}
- return $str;
+ return $value;
}
}
+// Writing text with formatting if trim($cell) != ''
+// See also: http://home.arino.jp/?tracker.inc.php%2F41
class Tracker_field_format extends Tracker_field
{
- var $sort_type = SORT_STRING;
+ var $sort_type = PLUGIN_TRACKER_SORT_TYPE_STRING;
+
var $styles = array();
var $formats = array();
- function Tracker_field_format($field, $page, $refer, & $config)
+ function Tracker_field_format(& $tracker_form, $field)
{
- parent::Tracker_field($field, $page, $refer, $config);
+ parent::Tracker_field($tracker_form, $field);
- foreach ($this->config->get($this->name) as $option) {
- list($key, $style, $format) =
- array_pad(array_map(create_function('$a', 'return trim($a);'), $option), 3, '');
+ 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;
if ($format != '') $this->formats[$key] = $format;
}
}
- function get_tag()
+ function get_key($value)
{
- return '<input type="text"' .
- ' name="' . htmlspecialchars($this->name) . '"' .
- ' size="' . htmlspecialchars($this->values[0]) . '" />';
+ return ($value == '') ? 'IS NULL' : 'IS NOT NULL';
}
- function get_key($str)
+ function get_tag()
{
- return ($str == '') ? 'IS NULL' : 'IS NOT NULL';
+ $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';
}
}
class Tracker_field_file extends Tracker_field_format
{
- var $sort_type = SORT_STRING;
+ var $sort_type = PLUGIN_TRACKER_SORT_TYPE_STRING;
function get_tag()
{
- return '<input type="file"' .
- ' name="' . htmlspecialchars($this->name) . '"' .
- ' size="' . htmlspecialchars($this->values[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 . '" />';
}
- function format_value($str)
+ function format_value()
{
if (isset($_FILES[$this->name])) {
-
require_once(PLUGIN_DIR . 'attach.inc.php');
- $result = attach_upload($_FILES[$this->name], $this->page);
- if ($result['result']) {
+ $base = $this->form->base;
+ $result = attach_upload($_FILES[$this->name], $base);
+ if (isset($result['result']) && $result['result']) {
// Upload success
- return parent::format_value($this->page . '/' . $_FILES[$this->name]['name']);
+ return parent::format_value($base . '/' . $_FILES[$this->name]['name']);
}
}
class Tracker_field_radio extends Tracker_field_format
{
- var $sort_type = SORT_NUMERIC;
+ var $sort_type = PLUGIN_TRACKER_SORT_TYPE_NUMERIC;
+ var $_options = array();
function get_tag()
{
$retval = '';
$id = 0;
- $s_name = htmlspecialchars($this->name);
- foreach ($this->config->get($this->name) as $option) {
+ $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]);
- $checked = trim($option[0]) == trim($this->default_value) ? ' checked="checked"' : '';
+ $s_option = htmlsc($option[0]);
+ $checked = trim($option[0]) === trim($this->default_value) ? ' checked="checked"' : '';
$retval .= '<input type="radio"' .
' name="' . $s_name . '"' .
return $retval;
}
- function get_key($str)
+ function get_value($value)
{
- return $str;
+ $options = & $this->_options;
+ $name = $this->name;
+
+ if (! isset($options[$name])) {
+ $values = array_map('reset', $this->form->config->get($name));
+ $options[$name] = array_flip($values); // array('value0' => 0, 'value1' => 1, ...)
+ }
+
+ return isset($options[$name][$value]) ? $options[$name][$value] : $value;
}
- function get_value($value)
+ // Revert(re-overload) Tracker_field_format's specific code
+ function get_key($value)
{
- static $options = array();
- if (! isset($options[$this->name])) {
- $options[$this->name] = array_flip(array_map(create_function('$arr', 'return $arr[0];'), $this->config->get($this->name)));
- }
- return isset($options[$this->name][$value]) ? $options[$this->name][$value] : $value;
+ return $value;
}
}
class Tracker_field_select extends Tracker_field_radio
{
- var $sort_type = SORT_NUMERIC;
+ var $sort_type = PLUGIN_TRACKER_SORT_TYPE_NUMERIC;
+
+ var $_defaults;
function get_tag($empty = FALSE)
{
- $s_name = htmlspecialchars($this->name);
- $s_size = (isset($this->values[0]) && is_numeric($this->values[0])) ?
- ' size="' . htmlspecialchars($this->values[0]) . '"' :
- '';
- $s_multiple = (isset($this->values[1]) && strtolower($this->values[1]) == 'multiple') ?
- ' multiple="multiple"' :
- '';
-
- $retval = '<select name="' . $s_name . '[]"' . $s_size . $s_multiple . '>' . "\n";
- if ($empty){
- $retval .= ' <option value=""></option>' . "\n";
+ if (! isset($this->_defaults)) {
+ $this->_defaults = array_flip(preg_split('/\s*,\s*/', $this->default_value, -1, PREG_SPLIT_NO_EMPTY));
}
- $defaults = array_flip(preg_split('/\s*,\s*/', $this->default_value, -1, PREG_SPLIT_NO_EMPTY));
- foreach ($this->config->get($this->name) as $option) {
- $s_option = htmlspecialchars($option[0]);
- $selected = isset($defaults[trim($option[0])]) ? ' selected="selected"' : '';
- $retval .= ' <option value="' . $s_option . '"' . $selected . '>' . $s_option . '</option>' . "\n";
+ $defaults = $this->_defaults;
+
+ $retval = array();
+
+ $s_name = htmlsc($this->name);
+ $s_size = (isset($this->options[0]) && is_numeric($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 . '>';
+
+ if ($empty) $retval[] = ' <option value=""></option>';
+
+ foreach ($this->form->config->get($this->name) as $option) {
+ $option = reset($option);
+ $s_option = htmlsc($option);
+ $selected = isset($defaults[trim($option)]) ? ' selected="selected"' : '';
+ $retval[] = ' <option value="' . $s_option . '"' . $selected . '>' . $s_option . '</option>';
}
- $retval .= '</select>';
- return $retval;
+ $retval[] = '</select>';
+
+ return implode("\n", $retval);
}
}
class Tracker_field_checkbox extends Tracker_field_radio
{
- var $sort_type = SORT_NUMERIC;
+ var $sort_type = PLUGIN_TRACKER_SORT_TYPE_NUMERIC;
- function get_tag($empty=FALSE)
+ 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->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";
}
class Tracker_field_hidden extends Tracker_field_radio
{
- var $sort_type = SORT_NUMERIC;
+ var $sort_type = PLUGIN_TRACKER_SORT_TYPE_NUMERIC;
- function get_tag($empty=FALSE)
+ 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";
}
}
{
function get_tag()
{
- $s_title = htmlspecialchars($this->title);
- $s_page = htmlspecialchars($this->page);
- $s_refer = htmlspecialchars($this->refer);
- $s_config = htmlspecialchars($this->config->config_name);
+ $form = $this->form;
+
+ $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" />
-<input type="hidden" name="plugin" value="tracker" />
-<input type="hidden" name="_refer" value="$s_refer" />
-<input type="hidden" name="_base" value="$s_page" />
+<input type="hidden" name="plugin" value="tracker" />
+<input type="hidden" name="_refer" value="$s_refer" />
+<input type="hidden" name="_base" value="$s_base" />
<input type="hidden" name="_config" value="$s_config" />
EOD;
}
class Tracker_field_date extends Tracker_field
{
- var $sort_type = SORT_NUMERIC;
+ var $sort_type = PLUGIN_TRACKER_SORT_TYPE_NUMERIC;
function format_cell($timestamp)
{
class Tracker_field_past extends Tracker_field
{
- var $sort_type = SORT_NUMERIC;
+ var $sort_type = PLUGIN_TRACKER_SORT_TYPE_NUMERIC;
- function format_cell($timestamp)
+ function get_value($timestamp)
{
- return get_passage($timestamp, FALSE);
+ return UTIME - $timestamp;
}
- function get_value($value)
+ function format_cell($timestamp)
{
- return UTIME - $value;
+ return get_passage($timestamp, FALSE);
}
}
///////////////////////////////////////////////////////////////////////////
+// tracker_list plugin
function plugin_tracker_list_convert()
{
global $vars;
- $config = 'default';
- $page = $refer = $vars['page'];
- $field = '_page';
+ $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 = 'list';
+ $list = '';
$limit = NULL;
- if (func_num_args()) {
- $args = func_get_args();
- switch (count($args)) {
- case 4:
- $limit = is_numeric($args[3]) ? $args[3] : $limit;
- case 3:
- $order = $args[2];
- case 2:
- $args[1] = get_fullname($args[1], $page);
- $page = is_pagename($args[1]) ? $args[1] : $page;
- case 1:
- $config = ($args[0] != '') ? $args[0] : $config;
- list($config, $list) = array_pad(explode('/', $config, 2), 2, $list);
- }
- }
- return plugin_tracker_getlist($page, $refer, $config, $list, $order, $limit);
+ switch ($argc) {
+ case 4: $limit = $args[3]; /*FALLTHROUGH*/
+ case 3: $order = $args[2]; /*FALLTHROUGH*/
+ case 2: $rel = $args[1]; /*FALLTHROUGH*/
+ case 1:
+ // Set "$config/$list"
+ if ($args[0] != '') {
+ $arg = explode('/', $args[0], 2);
+ if ($arg[0] != '' ) $config = $arg[0];
+ if (isset($arg[1])) $list = $arg[1];
+ }
+ }
+
+ unset($args, $argc, $arg);
+
+ return plugin_tracker_list_render($base, $refer, $rel, $config, $order, $list, $limit);
}
function plugin_tracker_list_action()
{
- global $script, $vars, $_tracker_messages;
+ global $get;
+
+ $base = isset($get['base']) ? $get['base'] : '';
+ $refer = isset($get['refer']) ? $get['refer'] : '';
+ $rel = '';
+ $config = isset($get['config']) ? $get['config'] : '';
+ $order = isset($get['order']) ? $get['order'] : '';
+ $list = isset($get['list']) ? $get['list'] : '';
+ $limit = isset($get['limit']) ? $get['limit'] : NULL;
- $page = $refer = $vars['refer'];
- $s_page = make_pagelink($page);
- $config = $vars['config'];
- $list = isset($vars['list']) ? $vars['list'] : 'list';
- $order = isset($vars['order']) ? $vars['order'] : '_real:SORT_DESC';
+ $s_refer = make_pagelink($refer);
return array(
- 'msg' => $_tracker_messages['msg_list'],
- 'body'=> str_replace('$1', $s_page, $_tracker_messages['msg_back']).
- plugin_tracker_getlist($page, $refer, $config, $list, $order)
+ 'msg' => plugin_tracker_message('msg_list'),
+ '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_getlist($page, $refer, $config_name, $list, $order = '', $limit = NULL)
+function plugin_tracker_list_render($base, $refer, $rel = '', $config = '', $order = '', $list = '', $limit = NULL)
{
- $config = new Config('plugin/tracker/' . $config_name);
+ $tracker_list = & new Tracker_list();
- if (! $config->read()) {
- return '<p>config file \'' . htmlspecialchars($config_name) . '\' is not exist.</p>';
+ if (! $tracker_list->init($base, $refer, $config, $rel) ||
+ ! $tracker_list->setSortOrder($order)) {
+ return '#tracker_list: ' . htmlsc($tracker_list->error) . '<br />';
}
- $config->config_name = $config_name;
+ if (! is_page($tracker_list->form->refer)) {
+ return '#tracker_list: Refer page not found: ' . htmlsc($refer) . '<br />';
+ }
- if (! is_page($config->page . '/' . $list)) {
- return '<p>config file \'' . make_pagelink($config->page . '/' . $list) . '\' not found.</p>';
+ $result = $tracker_list->toString($list, $limit);
+ if ($result === FALSE) {
+ return '#tracker_list: ' . htmlsc($tracker_list->error) . '<br />';
}
+ unset($tracker_list);
- $list = & new Tracker_list($page, $refer, $config, $list);
- $list->sort($order);
- return $list->toString($limit);
+ return convert_html($result);
}
// Listing class
class Tracker_list
{
- var $page;
- var $config;
- var $list;
- var $fields;
+ var $form; // class Tracker_form
+
+ var $rows = array();
+ var $orders;
+ var $error = ''; // Error message
+
+ // _generate_regex()
var $pattern;
var $pattern_fields;
- var $rows;
- var $order;
-
- function Tracker_list($page, $refer, & $config, $list)
- {
- $this->page = $page;
- $this->config = & $config;
- $this->list = $list;
- $this->fields = plugin_tracker_get_fields($page, $refer, $config);
- $this->pattern = '';
- $this->pattern_fields = array();
- $this->rows = array();
- $this->order = array();
-
- $pattern = plugin_tracker_get_source($config->page . '/page', TRUE);
-
- // Convert block-plugins to fields
- // Incleasing and decreasing around #comment etc, will be covererd with [_block_xxx]
- $pattern = preg_replace('/^\#([^\(\s]+)(?:\((.*)\))?\s*$/m', '[_block_$1]', $pattern);
-
- // Generate regexes
- $pattern = preg_split('/\\\\\[(\w+)\\\\\]/', preg_quote($pattern, '/'), -1, PREG_SPLIT_DELIM_CAPTURE);
- while (! empty($pattern)) {
- $this->pattern .= preg_replace('/\s+/', '\\s*', '(?>\\s*' . trim(array_shift($pattern)) . '\\s*)');
- if (! empty($pattern)) {
- $field = array_shift($pattern);
- $this->pattern_fields[] = $field;
- $this->pattern .= '(.*)';
- }
+
+ // add()
+ var $_added = array();
+
+ // toString()
+ var $_list;
+ var $_row;
+ var $_the_first_character_of_the_line;
+
+ function init($base, $refer, $config = NULL, $relative = '')
+ {
+ $this->form = & new Tracker_form();
+ return $this->form->init($base, $refer, $config, $relative);
+ }
+
+ // Generate/Regenerate regex to load one page
+ function _generate_regex()
+ {
+ if (isset($this->pattern) && isset($this->pattern_fields)) return TRUE;
+
+ $template_page = $this->form->config->page . '/' . 'page';
+ $fields = $this->form->fields;
+
+ $pattern = array();
+ $pattern_fields = array();
+
+ $template = plugin_tracker_get_source($template_page, TRUE);
+ if ($template === FALSE || empty($template)) {
+ $this->error = 'Page not found or seems empty: ' . $template_page;
+ return FALSE;
}
- // Listing
- $pattern = $page . '/';
- $pattern_len = strlen($pattern);
- foreach (get_existpages() as $_page) {
- if (strpos($_page, $pattern) === 0) {
- $name = substr($_page, $pattern_len);
- if (preg_match(PLUGIN_TRACKER_LIST_EXCLUDE_PATTERN, $name)) continue;
- $this->add($_page, $name);
+ // Block-plugins to pseudo fields (#convert => [_block_convert])
+ $template = preg_replace('/^\#([^\(\s]+)(?:\((.*)\))?\s*$/m', '[_block_$1]', $template);
+
+ // Now, $template = array('*someting*', 'fieldname', '*someting*', 'fieldname', ...)
+ $template = preg_split('/\\\\\[(\w+)\\\\\]/', preg_quote($template, '/'), -1, PREG_SPLIT_DELIM_CAPTURE);
+
+ // NOTE: if the page has garbages between [field]s, it will fail to be load
+ while (! empty($template)) {
+ // Just ignore these _fixed_ data
+ $pattern[] = preg_replace('/\s+/', '\\s*', '(?>\\s*' . trim(array_shift($template)) . '\\s*)');
+ if (empty($template)) continue;
+
+ $fieldname = array_shift($template);
+ if (isset($fields[$fieldname])) {
+ $pattern[] = '(.*?)'; // Just capture it
+ $pattern_fields[] = $fieldname; // Capture it as this $filedname
+ } else {
+ $pattern[] = '.*?'; // Just ignore pseudo fields etc
}
}
+
+ $this->pattern = '/' . implode('', $pattern) . '/sS';
+ $this->pattern_fields = $pattern_fields;
+
+ return TRUE;
}
- function add($page, $name)
+ // Adding $this->rows
+ // Add multiple pages at a time
+ function loadRows()
{
- static $done = array();
+ $base = $this->form->base . '/';
+ $len = strlen($base);
+ $regex = '#^' . preg_quote($base, '#') . '#';
- if (isset($done[$page])) return;
+ 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;
- $done[$page] = TRUE;
+ if (isset($this->_added[$pagename])) return TRUE;
+ $this->_added[$pagename] = TRUE;
- $source = plugin_tracker_get_source($page);
+ $source = plugin_tracker_get_source($pagename, TRUE);
+ if ($source === FALSE) $source = '';
- // Compat: 'move to [[page]]' (bugtrack plugin)
+ // Compat: 'move to [[page]]' (like bugtrack plugin)
$matches = array();
- if (! empty($source) && preg_match('/move\sto\s(.+)/', $source[0], $matches)) {
+ if (! $rescan && ! empty($source) && preg_match('/move\sto\s(.+)/', $source, $matches)) {
$to_page = strip_bracket(trim($matches[1]));
- if (! is_page($to_page)) {
- return; // Invalid
- } else {
- return $this->add($to_page, $name); // Rescan
+ if (is_page($to_page)) {
+ unset($source, $matches); // Release
+ return $this->addRow($to_page, TRUE); // Recurse(Rescan) once
}
}
- // Default
- $this->rows[$name] = array(
- '_page' => '[[' . $page . ']]',
- '_refer' => $this->page,
- '_real' => $name,
- '_update' => get_filetime($page),
- '_past' => get_filetime($page),
- '_match' => FALSE,
+ // Default column
+ $filetime = get_filetime($pagename);
+ $row = array(
+ // column => default data of the cell
+ '_page' => $pagename, // TODO: Redudant column pair [1]
+ '_real' => $pagename, // TODO: Redudant column pair [1]
+ '_update' => $filetime, // TODO: Redudant column pair [2]
+ '_past' => $filetime, // TODO: Redudant column pair [2]
);
- // Redefine
+ // Load / Redefine cell
$matches = array();
- $this->rows[$name]['_match'] =
- preg_match('/' . $this->pattern . '/s', implode('', $source), $matches);
- unset($source);
-
- if ($this->rows[$name]['_match']) {
- array_shift($matches);
- foreach ($this->pattern_fields as $key => $field) {
- $this->rows[$name][$field] = trim($matches[$key]);
+ if (preg_match($this->pattern, $source, $matches)) {
+ array_shift($matches); // $matches[0] = all of the captured string
+ foreach ($this->pattern_fields as $key => $fieldname) {
+ $row[$fieldname] = trim($matches[$key]);
+ unset($matches[$key]);
}
+ $this->rows[] = $row;
+ } else if (PLUGIN_TRACKER_LIST_SHOW_ERROR_PAGE) {
+ $this->rows[] = $row; // Error
}
+
+ return TRUE;
}
- function sort($order)
+ // setSortOrder()
+ function _order_commands2orders($order_commands = '')
{
- if ($order == '') return;
+ $order_commands = trim($order_commands);
+ if ($order_commands == '') $order_commands = PLUGIN_TRACKER_DEFAULT_ORDER;
+ if ($order_commands == '') return array();
+
+ $orders = array();
+
+ $i = 0;
+ foreach (explode(';', $order_commands) as $command) {
+ $command = trim($command);
+ if ($command == '') continue;
+
+ $arg = explode(':', $command, 2);
+ $fieldname = isset($arg[0]) ? trim($arg[0]) : '';
+ $order = isset($arg[1]) ? trim($arg[1]) : '';
+
+ $_order = $this->_sortkey_string2define($order);
+ if ($_order === FALSE) {
+ $this->error = 'Invalid sort key: ' . $order;
+ return FALSE;
+ } else if (isset($orders[$fieldname])) {
+ $this->error = 'Sort key already set for: ' . $fieldname;
+ return FALSE;
+ }
- $names = array_flip(array_keys($this->fields));
- $this->order = array();
+ if (PLUGIN_TRACKER_LIST_SORT_LIMIT <= $i) continue;
+ ++$i;
- foreach (explode(';', $order) as $item) {
- list($key, $dir) = array_pad(explode(':', $item), 1, 'ASC');
- if (! isset($names[$key])) continue;
+ $orders[$fieldname] = $_order;
+ }
- switch (strtoupper($dir)) {
- case 'SORT_ASC':
- case 'ASC':
- case SORT_ASC:
- $dir = SORT_ASC;
- break;
- case 'SORT_DESC':
- case 'DESC':
- case SORT_DESC:
- $dir = SORT_DESC;
- break;
- default:
- continue;
- }
- $this->order[$key] = $dir;
+ return $orders;
+ }
+
+ // Set commands for sort()
+ function setSortOrder($order_commands = '')
+ {
+ $orders = $this->_order_commands2orders($order_commands);
+ if ($orders === FALSE) {
+ unset($this->orders);
+ return FALSE;
+ } else {
+ $this->orders = $orders;
+ return TRUE;
}
- $keys = array();
- $params = array();
- foreach ($this->order as $field => $order) {
- if (! isset($names[$field])) continue;
+ }
- foreach ($this->rows as $row) {
- $keys[$field][] = isset($row[$field])?
- $this->fields[$field]->get_value($row[$field]) :
- '';
- }
- $params[] = $keys[$field];
- $params[] = $this->fields[$field]->sort_type;
- $params[] = $order;
+ // sortRows(): Internal sort type => PHP sort define
+ function _sort_type_dropout($order)
+ {
+ switch ($order) {
+ case PLUGIN_TRACKER_SORT_TYPE_REGULAR: return SORT_REGULAR;
+ case PLUGIN_TRACKER_SORT_TYPE_NUMERIC: return SORT_NUMERIC;
+ case PLUGIN_TRACKER_SORT_TYPE_STRING: return SORT_STRING;
+ case PLUGIN_TRACKER_SORT_TYPE_NATURAL: return SORT_NATURAL;
+ default:
+ $this->error = 'Invalid sort type';
+ return FALSE;
}
- $params[] = & $this->rows;
+ }
- call_user_func_array('array_multisort', $params);
+ // sortRows(): Internal sort order => PHP sort define
+ function _sort_order_dropout($order)
+ {
+ switch ($order) {
+ case PLUGIN_TRACKER_SORT_ORDER_ASC: return SORT_ASC;
+ case PLUGIN_TRACKER_SORT_ORDER_DESC: return SORT_DESC;
+ default:
+ $this->error = 'Invalid sort order';
+ return FALSE;
+ }
}
- // Used with preg_replace_callback() at toString()
- function replace_item($arr)
+ // Sort $this->rows by $this->orders
+ function sortRows()
{
- $params = explode(',', $arr[1]);
- $name = array_shift($params);
- if ($name == '') {
- $str = '';
- } else if (isset($this->items[$name])) {
- $str = $this->items[$name];
- if (isset($this->fields[$name])) {
- $str = $this->fields[$name]->format_cell($str);
+ if (! isset($this->orders)) {
+ $this->error = "Sort order seems not set";
+ return FALSE;
+ }
+
+ $fields = $this->form->fields;
+ $orders = $this->orders;
+ $types = array();
+
+ $fieldnames = array_keys($orders); // Field names to sort
+
+ foreach ($fieldnames as $fieldname) {
+ if (! isset($fields[$fieldname])) {
+ $this->error = 'No such field: ' . $fieldname;
+ return FALSE;
}
- } else {
- return $this->pipe ? str_replace('|', '|', $arr[0]) : $arr[0];
+ $types[$fieldname] = $this->_sort_type_dropout($fields[$fieldname]->sort_type);
+ $orders[$fieldname] = $this->_sort_order_dropout($orders[$fieldname]);
+ if ($types[$fieldname] === FALSE || $orders[$fieldname] === FALSE) return FALSE;
}
- $style = empty($params) ? $name : $params[0];
- if (isset($this->items[$style]) && isset($this->fields[$style])) {
- $str = sprintf($this->fields[$style]->get_style($this->items[$style]), $str);
+ $columns = array();
+ foreach ($this->rows as $row) {
+ foreach ($fieldnames as $fieldname) {
+ if (isset($row[$fieldname])) {
+ $columns[$fieldname][] = $fields[$fieldname]->get_value($row[$fieldname]);
+ } else {
+ $columns[$fieldname][] = '';
+ }
+ }
}
- return $this->pipe ? str_replace('|', '|', $str) : $str;
+ $params = array();
+ foreach ($fieldnames as $fieldname) {
+
+ if ($types[$fieldname] == SORT_NATURAL) {
+ $column = & $columns[$fieldname];
+ natcasesort($column);
+ $i = 0;
+ $last = NULL;
+ foreach (array_keys($column) as $key) {
+ // Consider the same values there, for array_multisort()
+ if ($last !== $column[$key]) ++$i;
+ $last = strtolower($column[$key]); // natCASEsort()
+ $column[$key] = $i;
+ }
+ ksort($column, SORT_NUMERIC); // Revert the order
+ $types[$fieldname] = SORT_NUMERIC;
+ }
+
+ // One column set (one-dimensional array, sort type, and sort order)
+ // for array_multisort()
+ $params[] = $columns[$fieldname];
+ $params[] = $types[$fieldname];
+ $params[] = $orders[$fieldname];
+ }
+ if (! empty($orders) && ! empty($this->rows)) {
+ $params[] = & $this->rows; // The target
+ call_user_func_array('array_multisort', $params);
+ }
+
+ return TRUE;
+ }
+
+ // toString(): Sort key: Define to string (internal var => string)
+ function _sortkey_define2string($sortkey)
+ {
+ switch ($sortkey) {
+ case PLUGIN_TRACKER_SORT_ORDER_ASC: return 'asc';
+ case PLUGIN_TRACKER_SORT_ORDER_DESC: return 'desc';
+ default:
+ $this->error = 'No such define: ' . $sortkey;
+ return FALSE;
+ }
}
- // Used with preg_replace_callback() at toString()
- function replace_title($arr)
+ // toString(): Sort key: String to define (string => internal var)
+ function _sortkey_string2define($sortkey)
{
- global $script;
+ switch (strtoupper(trim($sortkey))) {
+ case '': return PLUGIN_TRACKER_SORT_ORDER_DEFAULT; break;
+
+ case SORT_ASC: /*FALLTHROUGH*/ // Compat, will be removed at 1.4.9 or later
+ case 'SORT_ASC': /*FALLTHROUGH*/
+ case 'ASC': return PLUGIN_TRACKER_SORT_ORDER_ASC;
+
+ case SORT_DESC: /*FALLTHROUGH*/ // Compat, will be removed at 1.4.9 or later
+ case 'SORT_DESC': /*FALLTHROUGH*/
+ case 'DESC': return PLUGIN_TRACKER_SORT_ORDER_DESC;
+
+ default:
+ $this->error = 'Invalid sort key: ' . $sortkey;
+ return FALSE;
+ }
+ }
+
+ // toString(): Called within preg_replace_callback()
+ function _replace_title($matches = array())
+ {
+ $form = $this->form;
+ $base = $form->base;
+ $refer = $form->refer;
+ $fields = $form->fields;
+ $config = $form->config_name;
+
+ $orders = $this->orders;
+ $list = $this->_list;
+
+ $fieldname = isset($matches[1]) ? $matches[1] : '';
+ if (! isset($fields[$fieldname])) {
+ // Invalid $fieldname or user's own string or something. Nothing to do
+ return isset($matches[0]) ? $matches[0] : '';
+ }
- $field = $sort = $arr[1];
- if (! isset($this->fields[$field])) return $arr[0];
+ // This column seems sorted or not
+ if (isset($orders[$fieldname])) {
+ $is_asc = ($orders[$fieldname] == PLUGIN_TRACKER_SORT_ORDER_ASC);
- if ($sort == '_name' || $sort == '_page') $sort = '_real';
+ $indexes = array_flip(array_keys($orders));
+ $index = $indexes[$fieldname] + 1;
+ unset($indexes);
- $dir = SORT_ASC;
- $arrow = '';
- $order = $this->order;
- if (is_array($order) && isset($order[$sort])) {
- // BugTrack2/106: Only variables can be passed by reference from PHP 5.0.5
- $order_keys = array_keys($order); // with array_shift();
+ $arrow = '&br;' . ($is_asc ? '↑' : '↓') . '(' . $index . ')';
+ // Allow flip, if this is the first column
+ if (($index == 1) xor $is_asc) {
+ $order = PLUGIN_TRACKER_SORT_ORDER_ASC;
+ } else {
+ $order = PLUGIN_TRACKER_SORT_ORDER_DESC;
+ }
+ } else {
+ $arrow = '';
+ $order = PLUGIN_TRACKER_SORT_ORDER_DEFAULT;
+ }
- $index = array_flip($order_keys);
- $pos = 1 + $index[$sort];
- $b_end = ($sort == array_shift($order_keys));
- $b_order = ($order[$sort] == SORT_ASC);
- $dir = ($b_end xor $b_order) ? SORT_ASC : SORT_DESC;
- $arrow = '&br;' . ($b_order ? '↑' : '↓') . '(' . $pos . ')';
+ // This column will be the first position , if you click
+ $orders = array($fieldname => $order) + $orders;
- unset($order[$sort], $order_keys);
+ $_orders = array();
+ foreach ($orders as $_fieldname => $_order) {
+ if ($_order == PLUGIN_TRACKER_SORT_ORDER_DEFAULT) {
+ $_orders[] = $_fieldname;
+ } else {
+ $_orders[] = $_fieldname . ':' . $this->_sortkey_define2string($_order);
+ }
}
- $title = $this->fields[$field]->title;
- $r_page = rawurlencode($this->page);
- $r_config = rawurlencode($this->config->config_name);
- $r_list = rawurlencode($this->list);
- $_order = array($sort . ':' . $dir);
- if (is_array($order)) {
- foreach ($order as $key => $value) {
- $_order[] = $key . ':' . $value;
+
+ $script = get_script_uri();
+ $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
+ '[[' .
+ $fields[$fieldname]->title . $arrow .
+ '>' .
+ $script . '?plugin=tracker_list' .
+ '&refer=' . rawurlencode($refer) . // Try to show 'page title' properly
+ $r_base . $r_config . $r_list . $r_order .
+ ']]';
+ }
+
+ // toString(): Called within preg_replace_callback()
+ function _replace_item($matches = array())
+ {
+ $fields = $this->form->fields;
+ $row = $this->_row;
+ $tfc = $this->_the_first_character_of_the_line ;
+
+ $params = isset($matches[1]) ? explode(',', $matches[1]) : array();
+ $fieldname = isset($params[0]) ? $params[0] : '';
+ $stylename = isset($params[1]) ? $params[1] : $fieldname;
+
+ $str = '';
+
+ if ($fieldname != '') {
+ if (! isset($row[$fieldname])) {
+ // Maybe load miss of the page
+ if (isset($fields[$fieldname])) {
+ $str = '[match_err]'; // Exactlly
+ } else {
+ $str = isset($matches[0]) ? $matches[0] : ''; // Nothing to do
+ }
+ } else {
+ $str = $row[$fieldname];
+ if (isset($fields[$fieldname])) {
+ $str = $fields[$fieldname]->format_cell($str);
+ }
}
+ $str = plugin_tracker_escape($str, $tfc);
+ }
+
+ if (isset($fields[$stylename]) && isset($row[$stylename])) {
+ $_style = $fields[$stylename]->get_style($row[$stylename]);
+ $str = sprintf($_style, $str);
}
- $r_order = rawurlencode(join(';', $_order));
- return '[[' . $title . $arrow . '>' .
- $script . '?plugin=tracker_list&refer=' . $r_page .
- '&config=' . $r_config .
- '&list=' . $r_list . '&order=' . $r_order . ']]';
+ return $str;
}
- function toString($limit = NULL)
+ // Output a part of Wiki text
+ function toString($list = PLUGIN_TRACKER_DEFAULT_LIST, $limit = NULL)
{
- global $_tracker_messages;
+ $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;
$source = array();
+ $regex = '/\[([^\[\]]+)\]/';
+
+ // Loading template
+ $template = plugin_tracker_get_source($list, TRUE);
+ if ($template === FALSE || empty($template)) {
+ $this->error = 'List not found: ' . $list;
+ return FALSE;
+ }
+
+ // Try to create $form->fields just you need
+ if ($form->initFields('_real', plugin_tracker_field_pickup($template),
+ array_keys($this->orders)) === FALSE) {
+ $this->error = $form->error;
+ return FALSE;
+ }
+
+ // Load and sort $this->rows
+ if ($this->loadRows() === FALSE || $this->sortRows() === FALSE) return FALSE;
+ $rows = $this->rows;
+
+ // toString()
$count = count($this->rows);
- if ($limit !== NULL && $count > $limit) {
+ $limit = intval($limit);
+ if ($limit != 0) $limit = max(1, $limit);
+ if ($limit != 0 && $count > $limit) {
$source[] = str_replace(
- array('$1', '$2'),
+ array('$1', '$2' ),
array($count, $limit),
- $_tracker_messages['msg_limit']) . "\n";
- $this->rows = array_splice($this->rows, 0, $limit);
+ plugin_tracker_message('msg_limit')
+ ) . "\n";
+ $rows = array_slice($this->rows, 0, $limit);
}
- if (empty($this->rows)) return '';
- $body = array();
- foreach (plugin_tracker_get_source($this->config->page . '/' . $this->list) as $line) {
- if (preg_match('/^\|(.+)\|[hHfFcC]$/', $line)) {
- $source[] = preg_replace_callback('/\[([^\[\]]+)\]/', array(& $this, 'replace_title'), $line);
+ // Loading template
+ // TODO: How do you feel single/multiple table rows with 'c'(decolation)?
+ $matches = $t_header = $t_body = $t_footer = array();
+ $template = plugin_tracker_get_source($list);
+ if ($template === FALSE) {
+ $this->error = 'Page not found or seems empty: ' . $list;
+ return FALSE;
+ }
+ foreach ($template as $line) {
+ if (preg_match('/^\|.+\|([hfc])$/i', $line, $matches)) {
+ if (strtolower($matches[1]) == 'f') {
+ $t_footer[] = $line; // Table footer
+ } else {
+ $t_header[] = $line; // Table header, or decoration
+ }
} else {
- $body[] = $line;
+ $t_body[] = $line;
}
}
- foreach ($this->rows as $row) {
- if (! PLUGIN_TRACKER_LIST_SHOW_ERROR_PAGE && ! $row['_match']) continue;
+ unset($template);
- $this->items = $row;
- foreach ($body as $line) {
- if (ltrim($line) == '') {
- $source[] = $line;
- } else {
- $this->pipe = ($line{0} == '|' || $line{0} == ':');
- $source[] = preg_replace_callback('/\[([^\[\]]+)\]/', array(& $this, 'replace_item'), $line);
+ // Header and decolation
+ foreach($t_header as $line) {
+ $source[] = preg_replace_callback($regex, array(& $this, '_replace_title'), $line);
+ }
+ unset($t_header);
+ // Repeat
+ foreach ($rows as $row) {
+ $this->_row = $row;
+ // Body
+ foreach ($t_body as $line) {
+ if (ltrim($line) != '') {
+ $this->_the_first_character_of_the_line = $line[0];
+ $line = preg_replace_callback($regex, array(& $this, '_replace_item'), $line);
}
+ $source[] = $line;
}
}
+ unset($t_body);
+ // Footer
+ foreach($t_footer as $line) {
+ $source[] = preg_replace_callback($regex, array(& $this, '_replace_title'), $line);
+ }
+ unset($t_footer);
- return convert_html(implode('', $source));
+ return implode('', $source);
}
}
+// Roughly checking listed fields from template
+// " [field1] [field2,style1] " => array('fielld', 'field2')
+function plugin_tracker_field_pickup($string = '')
+{
+ if (! is_string($string) || empty($string)) return array();
+
+ $fieldnames = array();
+
+ $matches = array();
+ preg_match_all('/\[([^\[\]]+)\]/', $string, $matches);
+ unset($matches[0]);
+
+ foreach ($matches[1] as $match) {
+ $params = explode(',', $match, 2);
+ if (isset($params[0])) {
+ $fieldnames[$params[0]] = TRUE;
+ }
+ }
+
+ return array_keys($fieldnames);
+}
+
function plugin_tracker_get_source($page, $join = FALSE)
{
$source = get_source($page, TRUE, $join);
+ if ($source === FALSE) return FALSE;
- // Remove fixed-heading anchors
- $source = preg_replace('/^(\*{1,3}.*)\[#[A-Za-z][\w-]+\](.*)$/m', '$1$2', $source);
+ return preg_replace(
+ array(
+ '/^#freeze\s*$/im',
+ '/^(\*{1,3}.*)\[#[A-Za-z][\w-]+\](.*)$/m', // Remove fixed-heading anchors
+ ),
+ array(
+ '',
+ '$1$2',
+ ),
+ $source
+ );
+}
- // Remove #freeze-es
- return preg_replace('/^#freeze\s*$/im', '', $source);
+// Escape special characters not to break Wiki syntax
+function plugin_tracker_escape($string, $syntax_hint = '')
+{
+ // Default: line-oriented
+ $from = array("\n", "\r" );
+ $to = array('&br;', '&br;');
+
+ if ($syntax_hint == '|' || $syntax_hint == ':') {
+ // <table> or <dl> Wiki syntax: Excape '|'
+ $from[] = '|';
+ $to[] = '|';
+ } else if ($syntax_hint == ',') {
+ // <table> by comma
+ $from[] = ',';
+ $to[] = ',';
+ }
+ return str_replace($from, $to, $string);
}
+
+function plugin_tracker_message($key)
+{
+ global $_tracker_messages;
+ return isset($_tracker_messages[$key]) ? $_tracker_messages[$key] : 'NOMESSAGE';
+}
+
?>