CONF = &$CONF;
        if (empty($this->CONF)) {
            $this->CONF['RECENT_PAGE']  = 'RecentVotes';
            $this->CONF['RECENT_LOG']   = CACHE_DIR . 'recentvotes.dat';
            $this->CONF['RECENT_LIMIT'] = 100;
            $this->CONF['COOKIE_EXPIRED'] = 60*60*24*3;
            $this->CONF['BARCHART_LIB_FILE'] = LIB_DIR . 'barchart.cls.php';
            $this->CONF['BARCHART_COLOR_BAR'] = ' #0000cc';
            $this->CONF['BARCHART_COLOR_BG'] = 'transparent';
            $this->CONF['BARCHART_COLOR_BORDER'] = 'transparent';
        }
        static $default_options = array();
        $this->default_options = &$default_options;
        if (empty($this->default_options)) {
            $this->default_options['readonly'] = FALSE;
            $this->default_options['addchoice'] = FALSE;
            $this->default_options['barchart'] = FALSE;
        }
        // init
        $this->options  = $this->default_options;
        if (function_exists('textdomain')) {
            textdomain('vote'); // use i18n msgs of vote.inc.php
        }
    }
    
    function PluginVotex() {
    	$this->__construct();
    }
    // static
    var $CONF;
    var $default_options;
    // var
    var $options;
    /**
     * Action Plugin Main Function
     * @static
     */
    function action()
    {
        global $vars;
        if ($vars['pcmd'] === 'inline') {
            return $this->action_inline();
        } else {
            return $this->action_convert();
        }
    }
    /**
     * POST action via inline plugin
     */
    function action_inline()
    {
        global $vars, $defaultpage;
        $_title_collided   = _('On updating $1, a collision has occurred.');
        $_title_updated    = _('$1 was updated');
        $_msg_collided = _('It seems that someone has already updated this page while you were editing it.
 + is placed at the beginning of a line that was newly added.
 ! is placed at the beginning of a line that has possibly been updated.
 Edit those lines, and submit again.');
        
        if (method_exists('auth', 'check_role')) { // Plus!
            if (auth::check_role('readonly')) die_message('PKWK_READONLY prohibits editing');
        } else {
            if (PKWK_READONLY) die_message('PKWK_READONLY prohibits editing');
        }
        $page         = isset($vars['refer']) ? $vars['refer'] : $defaultpage;
        $pcmd         = $vars['pcmd'];
        $vote_id      = $vars['vote_id'];
        $vars['page'] = $page;
        $choice_id    = $vars['choice_id'];
        if ($this->is_continuous_vote($page, $pcmd, $vote_id)) {
            return array(
                         'msg'  => _('Error in vote'),
                         'body' => _('Continuation vote cannot be performed.'),
                         );
        }
        // parse contents of wiki page and get update
        $lines = get_source($page);
        list($linenum, $newline, $newtext, $newvotes) = $this->get_update_inline($lines, $vote_id, $choice_id);
        if ($linenum === false) {
            die_message(_('There was no matching vote. '));
        }
        $newlines = $lines;
        $newlines[$linenum] = $newline;
        $newcontents = implode('', $newlines);
        // collision check
        $contents = implode('', $lines);
        if (md5($contents) !== $vars['digest']) {
            $msg  = $_title_collided;
            $body = $this->show_preview_form($_msg_collided, $newline);
            return array('msg'=>$msg, 'body'=>$body);
        }
        page_write($page, $newcontents, TRUE); // notimestamp
        $this->update_recent_voted($page, $pcmd, $vote_id, $choice_id, $newvotes);
        //static in convert() was somehow wierd if return(msg=>'',body=>'');
        //$msg  = $_title_updated;
        //$body = '';
        //return array('msg'=>$msg, 'body'=>$body);
        $anchor = $this->get_anchor($pcmd, $vote_id);
        header('Location: ' . get_script_uri() . '?' . rawurlencode($page) . '#' . $anchor);
        exit;
    }
    /**
     * POST action via convert plugin
     */
    function action_convert()
    {
        global $vars, $defaultpage;
        $_title_collided   = _('On updating $1, a collision has occurred.');
        $_title_updated    = _('$1 was updated');
        $_msg_collided = _('It seems that someone has already updated this page while you were editing it.
 + is placed at the beginning of a line that was newly added.
 ! is placed at the beginning of a line that has possibly been updated.
 Edit those lines, and submit again.');
        
        if (method_exists('auth', 'check_role')) { // Plus!
            if (auth::check_role('readonly')) die_message('PKWK_READONLY prohibits editing');
        } else {
            if (PKWK_READONLY) die_message('PKWK_READONLY prohibits editing');
        }
        $page         = isset($vars['refer']) ? $vars['refer'] : $defaultpage;
        $pcmd         = $vars['pcmd'];
        $vote_id      = $vars['vote_id'];
        $vars['page'] = $page;
        $choice_id    = $this->get_selected_choice_convert();
        $addchoice    = isset($vars['addchoice']) && $vars['addchoice'] !== ''
            ? $vars['addchoice'] : null;
        
        if ($this->is_continuous_vote($page, $pcmd, $vote_id)) {
            return array(
                         'msg'  => _('Error in vote'),
                         'body' => _('Continuation vote cannot be performed.'),
                         );
        }
        // parse contents of wiki page and get update
        $lines = get_source($page);
        list($linenum, $newline, $newtext, $newvotes) = $this->get_update_convert($lines, $vote_id, $choice_id, $addchoice);
        if ($linenum === false) {
            die_message(_('There was no matching vote. '));
        }
        $newlines = $lines;
        $newlines[$linenum] = $newline;
        $newcontents = implode('', $newlines);
        // collision check
        $contents = implode('', $lines);
        if (md5($contents) !== $vars['digest']) {
            $msg  = $_title_collided;
            $body = $this->show_preview_form($_msg_collided, $newline);
            return array('msg'=>$msg, 'body'=>$body);
        }
        page_write($page, $newcontents, TRUE); // notimestamp
        if (isset($addchoice)) $choice_id = count($newvotes) - 1; // to make sure
        $this->update_recent_voted($page, $pcmd, $vote_id, $choice_id, $newvotes);
        //static in convert() was somehow wierd if return(msg=>'',body=>'');
        //$msg  = $_title_updated;
        //$body = '';
        //return array('msg'=>$msg, 'body'=>$body);
        $anchor = $this->get_anchor($pcmd, $vote_id);
        header('Location: ' . get_script_uri() . '?' . rawurlencode($page) . '#' . $anchor);
        exit;
    }
    /**
     * Update Vote for inline plugin
     *
     * @param array &$lines
     * @param integer $vote_id
     * @parram string $choice_id
     * @return array array($linenum, $updated_line, $updated_text, $updated_votes)
     */
    function get_update_inline(&$lines, $vote_id, $choice_id) 
    {
        $contents = implode('', $lines);
        global $vars, $defaultpage;
        $page = isset($vars['refer']) ? $vars['refer'] : $defaultpage;
        $ic = new InlineConverter(array('plugin'));
        $vote_count = 0;
        foreach ($lines as $linenum => $line) {
            if (strpos($line, ' ') === 0) continue; // skip pre
            $inlines = $ic->get_objects($line, $page);
            $pos = 0;
            foreach ($inlines as $inline) {
                if ($inline->name !== 'votex') continue;
                $pos = strpos($line, '&votex', $pos);
                if ($vote_id > $vote_count++) {
                    $pos++;
                } else {
                    $l_remain = substr($line, 0, $pos);
                    $r_remain = substr($line, $pos + strlen($inline->text));
                    $arg      = $inline->param;
                    $body     = $inline->body;
                    $args     = csv_explode(',', $arg);
                    list($votes, $options) = $this->parse_args_inline($args, $this->default_options);
                    if ($options['readonly']) return array(false, false, false, false);
                    foreach ($votes as $i => $vote) {
                        list($choice, $count) = $vote;
                        if ($i == $choice_id) {
                            ++$count;
                            $votes[$i] = array($choice, $count);
                        }
                    }
                    $new_args = $this->restore_args_inline($votes, $options, $this->default_options);
                    $new_arg  = csv_implode(',', $new_args);
                    $body = ($body != '') ? '{' . $body . '};' : ';';
                    $newtext = '&votex(' . $new_arg . ')' . $body;
                    $newline = $l_remain . $newtext . $r_remain;
                    return array($linenum, $newline, $newtext, $votes);
                }
            }
        }
        return array(false, false, false, false);
    }
    /**
     * Update Vote for convert plugin
     *
     * @param array &$lines
     * @param integer $vote_id
     * @parram string $choice_id
     * @param string $addchoice
     * @return array array($linenum, $updated_line, $updated_text, $updated_votes)
     */
    function get_update_convert(&$lines, $vote_id, $choice_id, $addchoice = null) 
    {
        $vote_count  = 0;
        foreach($lines as $linenum => $line) {
            $matches = array();
            if (preg_match('/^#votex(?:\((.*)\)(.*))?$/i', $line, $matches)
                && $vote_id == $vote_count++) {
                $args   = csv_explode(',', $matches[1]);
                $remain = isset($matches[2]) ? $matches[2] : '';
                list($votes, $options) = $this->parse_args_convert($args, $this->default_options);
                if ($options['readonly']) return array(false, false, false, false);
                if (isset($addchoice)) {
                    $votes[] = array($addchoice, 1);
                } elseif (isset($votes[$choice_id])) {
                    list($choice, $count) = $votes[$choice_id];
                    $votes[$choice_id] = array($choice, $count + 1);
                }
                $new_args = $this->restore_args_convert($votes, $options, $this->default_options);
                $new_arg  = csv_implode(',', $new_args);
                $newtext = '#votex(' . $new_arg . ')';
                $newline = $newtext . $remain . "\n";
                return array($linenum, $newline, $newtext, $votes);
            }
        }
        return array(false, false, false, false);
    }
    /**
     * Get the selected choice id
     *
     * @global $vars;
     * @return string $choice_id
     * @uses decode_choice()
     */
    function get_selected_choice_convert()
    {
        global $vars;
        $choice_id = false;
        foreach ($vars as $key => $val) {
            if (strpos($key, 'choice_') === 0) {
                $choice_id = $this->decode_choice($key);
                break;
            }
        }
        return $choice_id;
    }
    /**
     * Recent Voted
     *
     * @param string $page voted page
     * @param string $pcmd convert or inline
     * @param integer $vote_id
     * @param integer $choice_id
     * @param array $votes
     * @return void
     */
    function update_recent_voted($page, $pcmd, $vote_id, $choice_id, $votes)
    {
        $limit = max(0, $this->CONF['RECENT_LIMIT']);
        $time = UTIME;
        // RecentVoted
        $lines = get_source($this->CONF['RECENT_PAGE']);
        $anchor  = $this->get_anchor($pcmd, $vote_id);
        $args = array();
        foreach ($votes as $vote) {
            list($choice, $count) = $vote;
            $args[] = $choice . '[' . $count . ']';
        }
        $arg = csv_implode(',', $args);
        list($choice, $count) = $votes[$choice_id];
        $addline =
            '-' . format_date($time) . 
            ' - [[' . $page . '#' . $vote_id . '>' . $page . '#' . $anchor . ']] ' .
            $choice . 
            ' (' . $arg . ')' .
            "\n";
        array_unshift($lines, $addline);
        $lines = array_splice($lines, 0, $limit);
        page_write($this->CONF['RECENT_PAGE'], implode('', $lines));
        // recentvoted.dat (serialization)
        if (is_readable($this->CONF['RECENT_LOG'])) {
            $log_contents = file_get_contents($this->CONF['RECENT_LOG']);
            $logs = unserialize($log_contents);
        } else {
            $logs = array();
        }
        $addlog = array($time, $page, $pcmd, $vote_id, $choice_id, $votes);
        array_unshift($logs, $addlog);
        $logs = array_splice($logs, 0, $limit);
        file_put_contents($this->CONF['RECENT_LOG'], serialize($logs));
    }
    /**
     * Check if a continuous vote
     *
     * @param string $page
     * @param string $pcmd convert or inline
     * @param integer $vote_id vote form id
     * @return boolean true if if is a continuous vote
     * @global $_COOKIE
     * @global $_SERVER
     * @vars $CONF 'COOKIE_EXPIRED'
     */
    function is_continuous_vote($page, $pcmd, $vote_id)
    {
        $cmd = 'votex';
        $votedkey = $cmd . '_' . $pcmd . '_' . $page . '_' . $vote_id;
        if (isset($_COOKIE[$votedkey])) {
            return true;
        }
        $_COOKIE[$votedkey] = 1;
        $matches = array();
        preg_match('!(.*/)!', $_SERVER['REQUEST_URI'], $matches);
        setcookie($votedkey, 1, time()+$this->CONF['COOKIE_EXPIRED'], $matches[0]);
        return false;
    }
    /**
     * Get Preview Form HTML (for when collision occured)
     *
     * @param string $msg message
     * @param string $body
     * @return string
     */
    function show_preview_form($msg = '', $body = '')
    {
        global $vars, $rows, $cols;
        $s_refer  = htmlsc($vars['refer']);
        $s_digest = htmlsc($vars['digest']);
        $s_body   = htmlsc($body);
        $form  = '';
        $form .= $msg . "\n";
        $form .= '