xref: /plugin/todo/action.php (revision c5e3cb6d09aef72d930941d352f5eac026e6cdd0)
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 20131008 Gerrit Uitslag <klapinklapin@gmail.com> \n
55     *                move ajax.php to action.php, added lock and conflict checks and improved saving
56     * @date 20130405 Leo Eibler <dokuwiki@sprossenwanne.at> \n
57     *                replace old sack() method with new jQuery method and use post instead of get \n
58     * @date 20130407 Leo Eibler <dokuwiki@sprossenwanne.at> \n
59     *                add user assignment for todos \n
60     * @date 20130408 Christian Marg <marg@rz.tu-clausthal.de> \n
61     *                change only the clicked to-do item instead of all items with the same text \n
62     *                origVal is not used anymore, we use the index (occurrence) of input element \n
63     * @date 20130408 Leo Eibler <dokuwiki@sprossenwanne.at> \n
64     *                migrate changes made by Christian Marg to current version of plugin \n
65     *
66     *
67     * @param Doku_Event $event
68     * @param mixed $param not defined
69     */
70    public function _ajax_call(&$event, $param) {
71        global $ID;
72
73        if($event->data !== 'plugin_todo') {
74            return;
75        }
76        //no other ajax call handlers needed
77        $event->stopPropagation();
78        $event->preventDefault();
79
80        #Variables
81        // by einhirn <marg@rz.tu-clausthal.de> determine checkbox index by using class 'todocheckbox'
82
83        if(isset($_REQUEST['index'], $_REQUEST['checked'], $_REQUEST['pageid'])) {
84            // index = position of occurrence of <input> element (starting with 0 for first element)
85            $index = (int) $_REQUEST['index'];
86            // checked = flag if input is checked means to do is complete (1) or not (0)
87            $checked = (boolean) urldecode($_REQUEST['checked']);
88            // path = page ID
89            $ID = cleanID(urldecode($_REQUEST['pageid']));
90        } else {
91            return;
92        }
93
94        $date = 0;
95        if(isset($_REQUEST['date'])) $date = (int) $_REQUEST['date'];
96
97        $INFO = pageinfo();
98
99        #Determine Permissions
100        if(auth_quickaclcheck($ID) < AUTH_EDIT) {
101            echo "You do not have permission to edit this file.\nAccess was denied.";
102            return;
103        }
104        // Check, if page is locked
105        if(checklock($ID)) {
106            $this->printJson(array('message' => 'The page is currently locked.'));
107        }
108
109        //conflict check
110        if($date != 0 && $INFO['meta']['date']['modified'] > $date) {
111            $this->printJson(array('message' => 'A newer version of this page is available, refresh your page before trying again.'));
112            return;
113        }
114
115        #Retrieve Page Contents
116        $wikitext = rawWiki($ID);
117
118        #Determine position of tag
119        if($index >= 0) {
120            $index++;
121            // index is only set on the current page with the todos
122            // the occurances are counted, untill the index-th input is reached which is updated
123            $todoTagStartPos = $this->_strnpos($wikitext, '<todo', $index);
124            $todoTagEndPos = strpos($wikitext, '>', $todoTagStartPos) + 1;
125
126            if($todoTagEndPos > $todoTagStartPos) {
127                // update text
128                $oldTag = substr($wikitext, $todoTagStartPos, ($todoTagEndPos - $todoTagStartPos));
129                $newTag = $this->_buildTodoTag($oldTag, $checked);
130                $wikitext = substr_replace($wikitext, $newTag, $todoTagStartPos, ($todoTagEndPos - $todoTagStartPos));
131
132                // save Update (Minor)
133                lock($ID);
134                saveWikiText($ID, $wikitext, 'Checkbox Change', $minoredit = true);
135                unlock($ID);
136
137                $return = array(
138                    'date' => @filemtime(wikiFN($ID)),
139                    'succeed' => true
140                );
141                $this->printJson($return);
142            }
143        }
144    }
145
146    /**
147     * Encode and print an arbitrary variable into JSON format
148     *
149     * @param mixed $return
150     */
151    private function printJson($return) {
152        $json = new JSON();
153        echo $json->encode($return);
154    }
155
156    /**
157     * @brief gets current to-do tag and returns a new one depending on checked
158     * @param $todoTag    string current to-do tag e.g. <todo @user>
159     * @param $checked    int check flag (todo completed=1, todo uncompleted=0)
160     * @return string new to-do completed or uncompleted tag e.g. <todo @user #>
161     */
162    private function _buildTodoTag($todoTag, $checked) {
163        $x = preg_match('%<todo([^>]*)>%i', $todoTag, $matches);
164        $newTag = '<todo';
165        if($x) {
166            if(($userPos = strpos($matches[1], '@')) !== false) {
167                $submatch = substr($todoTag, $userPos);
168                $x = preg_match('%@([-.\w]+)%i', $submatch, $matchinguser);
169                if($x) {
170                    $newTag .= ' @' . $matchinguser[1];
171                }
172            }
173        }
174        if($checked == 1) {
175            $newTag .= ' #';
176        }
177        $newTag .= '>';
178        return $newTag;
179    }
180
181    /**
182     * Find position of $occurance-th $needle in haystack
183     */
184    private function _strnpos($haystack, $needle, $occurance, $pos = 0) {
185        for($i = 1; $i <= $occurance; $i++) {
186            $pos = strpos($haystack, $needle, $pos) + 1;
187        }
188        return $pos - 1;
189    }
190}