xref: /plugin/todo/action.php (revision 2e09bbe09f6f24976f896a881d158ca44be39f05)
1<?php
2
3/**
4 * ToDo Action Plugin: Inserts button for ToDo plugin into toolbar
5 *
6 * Original Example: http://www.dokuwiki.org/devel:action_plugins
7 * @author     Babbage <babbage@digitalbrink.com>
8 * @date 20130405 Leo Eibler <dokuwiki@sprossenwanne.at> \n
9 *                replace old sack() method with new jQuery method and use post instead of get \n
10 * @date 20130408 Leo Eibler <dokuwiki@sprossenwanne.at> \n
11 *                remove getInfo() call because it's done by plugin.info.txt (since dokuwiki 2009-12-25 Lemming)
12 */
13
14if(!defined('DOKU_INC')) die();
15/**
16 * Class action_plugin_todo registers actions
17 */
18class action_plugin_todo extends DokuWiki_Action_Plugin {
19
20    /**
21     * Register the eventhandlers
22     */
23    public function register(&$controller) {
24        $controller->register_hook('TOOLBAR_DEFINE', 'AFTER', $this, 'insert_button', array());
25        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, '_ajax_call', array());
26    }
27
28    /**
29     * Inserts the toolbar button
30     */
31    public function insert_button(&$event, $param) {
32        $event->data[] = array(
33            'type' => 'format',
34            'title' => $this->getLang('qb_todobutton'),
35            'icon' => '../../plugins/todo/todo.png',
36            'key' => 't',
37            'open' => '<todo>',
38            'close' => '</todo>',
39            'block' => false,
40        );
41    }
42
43    /**
44     * Handles ajax requests for to do plugin
45     *
46     * @brief This method is called by ajax if the user clicks on the to-do checkbox or the to-do text.
47     * It sets the to-do state to completed or reset it to open.
48     *
49     * POST Parameters:
50     *   index    int the position of the occurrence of the input element (starting with 0 for first element/to-do)
51     *   checked    int should the to-do set to completed (1) or to open (0)
52     *   path    string id/path/name of the page
53     *
54     * @date 20130405 Leo Eibler <dokuwiki@sprossenwanne.at> \n
55     *                replace old sack() method with new jQuery method and use post instead of get \n
56     * @date 20130407 Leo Eibler <dokuwiki@sprossenwanne.at> \n
57     *                add user assignment for todos \n
58     * @date 20130408 Christian Marg <marg@rz.tu-clausthal.de> \n
59     *                change only the clicked to-do item instead of all items with the same text \n
60     *                origVal is not used anymore, we use the index (occurrence) of input element \n
61     * @date 20130408 Leo Eibler <dokuwiki@sprossenwanne.at> \n
62     *                migrate changes made by Christian Marg to current version of plugin \n
63     *
64     *
65     * @param Doku_Event $event
66     * @param mixed $param not defined
67     */
68    public function _ajax_call(&$event, $param) {
69        global $ID;
70
71        if($event->data !== 'plugin_todo') {
72            return;
73        }
74        //no other ajax call handlers needed
75        $event->stopPropagation();
76        $event->preventDefault();
77
78        #Variables
79        // by einhirn <marg@rz.tu-clausthal.de> determine checkbox index by using class 'todocheckbox'
80
81        if(isset($_REQUEST['index'], $_REQUEST['checked'], $_REQUEST['path'])) {
82            // index = position of occurrence of <input> element (starting with 0 for first element)
83            $index = urldecode($_REQUEST['index']);
84            // checked = flag if input is checked means to do is complete (1) or not (0)
85            $checked = urldecode($_REQUEST['checked']);
86            // path = page ID (name)
87            $ID = cleanID(urldecode($_REQUEST['path']));
88        } else {
89            return;
90        }
91        $origVal = '';
92        if(isset($_REQUEST['origVal'])) {
93            // origVal = urlencoded original value (in the case this is called by dokuwiki searchpattern plugin rendered page)
94            $origVal = urldecode($_REQUEST['origVal']);
95        }
96
97        $INFO = pageinfo(); //FIXME is this same as global $INFO;?
98        $fileName = $INFO['filepath'];
99
100        #Determine Permissions
101        if(auth_quickaclcheck($ID) < AUTH_EDIT) {
102            echo "You do not have permission to edit this file.\nAccess was denied.";
103            return;
104        }
105
106        #Retrieve File Contents
107        $newContents = file_get_contents($fileName);
108
109        $contentChanged = false;
110        #Modify Contents
111
112        if($index >= 0) {
113            $index++;
114            // no origVal so we count all todos with the method from Christian Marg
115            // this will happen if we are on the current page with the todos
116            $todoPos = strnpos($newContents, '<todo', $index);
117            $todoTextPost = strpos($newContents, '>', $todoPos) + 1;
118            if($todoTextPost > $todoPos) {
119                $todoTag = substr($newContents, $todoPos, $todoTextPost - $todoPos);
120                $newTag = $this->_todoProcessTag($todoTag, $checked);
121                $newContents = substr_replace($newContents, $newTag, $todoPos, ($todoTextPost - $todoPos));
122                $contentChanged = true;
123            }
124        } else {
125            // this will happen if we are on a dokuwiki searchpattern plugin summary page
126            if($checked) {
127                $pattern = '/(<todo[^#>]*>(' . $this->_todoStr2regex($origVal) . '<\/todo[\W]*?>))/';
128            } else {
129                $pattern = '/(<todo[^#>]*#[^>]*>(' . $this->_todoStr2regex($origVal) . '<\/todo[\W]*?>))/';
130            }
131            $x = preg_match_all($pattern, $newContents, $spMatches, PREG_OFFSET_CAPTURE);
132            if($x && isset($spMatches[0][0])) {
133                // yes, we found matches and index is in a valid range
134                $todoPos = $spMatches[1][0][1];
135                $todoTextPost = $spMatches[2][0][1];
136                $todoTag = substr($newContents, $todoPos, $todoTextPost - $todoPos);
137                $newTag = $this->_todoProcessTag($todoTag, $checked);
138                $newContents = substr_replace($newContents, $newTag, $todoPos, ($todoTextPost - $todoPos));
139                $contentChanged = true;
140            }
141        }
142
143        if($contentChanged) {
144            #Save Update (Minor)
145            io_writeWikiPage($fileName, $newContents, $ID, '');
146            addLogEntry(saveOldRevision($ID), $ID, DOKU_CHANGE_TYPE_MINOR_EDIT, "Checkbox Change", '');
147        }
148
149    }
150
151#(Possible) Alternative Method
152//Retrieve mtime from file
153//Load Data
154//Modify Data
155//Save Data
156//Replace new mtime with previous one
157
158    /**
159     * @brief gets current to-do tag and returns a new one depending on checked
160     * @param $todoTag    string current to-do tag e.g. <todo @user>
161     * @param $checked    int check flag (todo completed=1, todo uncompleted=0)
162     * @return string new to-do completed or uncompleted tag e.g. <todo @user #>
163     */
164    private function _todoProcessTag($todoTag, $checked) {
165        $x = preg_match('%<todo([^>]*)>%i', $todoTag, $pregmatches);
166        $newTag = '<todo';
167        if($x) {
168            if(($uPos = strpos($pregmatches[1], '@')) !== false) {
169                $match2 = substr($todoTag, $uPos);
170                $x = preg_match('%@([-.\w]+)%i', $match2, $pregmatches);
171                if($x) {
172                    $todo_user = $pregmatches[1];
173                    $newTag .= ' @' . $todo_user;
174                }
175            }
176        }
177        if($checked == 1) {
178            $newTag .= ' #';
179        }
180        $newTag .= '>';
181        return $newTag;
182    }
183
184    /**
185     * @brief Convert a string to a regex so it can be used in PHP "preg_match" function
186     * from dokuwiki searchpattern plugin
187     */
188    private function _todoStr2regex($str) {
189        $regex = ''; //init
190        for($i = 0; $i < strlen($str); $i++) { //for each char in the string
191            if(!ctype_alnum($str[$i])) { //if char is not alpha-numeric
192                $regex = $regex . '\\'; //escape it with a backslash
193            }
194            $regex = $regex . $str[$i]; //compose regex
195        }
196        return $regex; //return
197    }
198
199}
200
201
202if(!function_exists('strnpos')) {
203    /**
204     * Find position of $occurance-th $needle in haystack
205     */
206    function strnpos($haystack, $needle, $occurance, $pos = 0) {
207        for($i = 1; $i <= $occurance; $i++) {
208            $pos = strpos($haystack, $needle, $pos) + 1;
209        }
210        return $pos - 1;
211    }
212}