xref: /plugin/todo/action.php (revision 4afeeeed305c878554707702b2c239483fb72a25)
163314d65SLeo Eibler<?php
263314d65SLeo Eibler
363314d65SLeo Eibler/**
463314d65SLeo Eibler * ToDo Action Plugin: Inserts button for ToDo plugin into toolbar
563314d65SLeo Eibler *
663314d65SLeo Eibler * Original Example: http://www.dokuwiki.org/devel:action_plugins
763314d65SLeo Eibler * @author     Babbage <babbage@digitalbrink.com>
816a9e697SLeo Eibler * @date 20130405 Leo Eibler <dokuwiki@sprossenwanne.at> \n
916a9e697SLeo Eibler *                replace old sack() method with new jQuery method and use post instead of get \n
104f445722SLeo Eibler * @date 20130408 Leo Eibler <dokuwiki@sprossenwanne.at> \n
11aa11d6a4Sleibler *                remove getInfo() call because it's done by plugin.info.txt (since dokuwiki 2009-12-25 Lemming)
1263314d65SLeo Eibler */
1363314d65SLeo Eibler
1463314d65SLeo Eiblerif(!defined('DOKU_INC')) die();
1563314d65SLeo Eibler/**
16aa11d6a4Sleibler * Class action_plugin_todo registers actions
1763314d65SLeo Eibler */
18aa11d6a4Sleiblerclass action_plugin_todo extends DokuWiki_Action_Plugin {
1963314d65SLeo Eibler
2063314d65SLeo Eibler    /**
2163314d65SLeo Eibler     * Register the eventhandlers
2263314d65SLeo Eibler     */
23aa11d6a4Sleibler    public function register(Doku_Event_Handler $controller) {
2463314d65SLeo Eibler        $controller->register_hook('TOOLBAR_DEFINE', 'AFTER', $this, 'insert_button', array());
25aa11d6a4Sleibler        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, '_ajax_call', array());
2663314d65SLeo Eibler    }
2763314d65SLeo Eibler
2863314d65SLeo Eibler    /**
2963314d65SLeo Eibler     * Inserts the toolbar button
3063314d65SLeo Eibler     */
31aa11d6a4Sleibler    public function insert_button(&$event, $param) {
3263314d65SLeo Eibler        $event->data[] = array(
3363314d65SLeo Eibler            'type' => 'format',
3463314d65SLeo Eibler            'title' => $this->getLang('qb_todobutton'),
3563314d65SLeo Eibler            'icon' => '../../plugins/todo/todo.png',
36e2ffa8f7Srunout-at// key 't' is already used for going to top of page, bug #76
37e2ffa8f7Srunout-at//	    'key' => 't',
3863314d65SLeo Eibler            'open' => '<todo>',
3963314d65SLeo Eibler            'close' => '</todo>',
4016a9e697SLeo Eibler            'block' => false,
4163314d65SLeo Eibler        );
4263314d65SLeo Eibler    }
4363314d65SLeo Eibler
44aa11d6a4Sleibler    /**
45aa11d6a4Sleibler     * Handles ajax requests for to do plugin
46aa11d6a4Sleibler     *
47aa11d6a4Sleibler     * @brief This method is called by ajax if the user clicks on the to-do checkbox or the to-do text.
48aa11d6a4Sleibler     * It sets the to-do state to completed or reset it to open.
49aa11d6a4Sleibler     *
50aa11d6a4Sleibler     * POST Parameters:
51aa11d6a4Sleibler     *   index    int the position of the occurrence of the input element (starting with 0 for first element/to-do)
52aa11d6a4Sleibler     *   checked    int should the to-do set to completed (1) or to open (0)
53aa11d6a4Sleibler     *   path    string id/path/name of the page
54aa11d6a4Sleibler     *
55aa11d6a4Sleibler     * @date 20140317 Leo Eibler <dokuwiki@sprossenwanne.at> \n
56aa11d6a4Sleibler     *                use todo content as change description \n
57aa11d6a4Sleibler     * @date 20131008 Gerrit Uitslag <klapinklapin@gmail.com> \n
58aa11d6a4Sleibler     *                move ajax.php to action.php, added lock and conflict checks and improved saving
59aa11d6a4Sleibler     * @date 20130405 Leo Eibler <dokuwiki@sprossenwanne.at> \n
60aa11d6a4Sleibler     *                replace old sack() method with new jQuery method and use post instead of get \n
61aa11d6a4Sleibler     * @date 20130407 Leo Eibler <dokuwiki@sprossenwanne.at> \n
62aa11d6a4Sleibler     *                add user assignment for todos \n
63aa11d6a4Sleibler     * @date 20130408 Christian Marg <marg@rz.tu-clausthal.de> \n
64aa11d6a4Sleibler     *                change only the clicked to-do item instead of all items with the same text \n
65aa11d6a4Sleibler     *                origVal is not used anymore, we use the index (occurrence) of input element \n
66aa11d6a4Sleibler     * @date 20130408 Leo Eibler <dokuwiki@sprossenwanne.at> \n
67aa11d6a4Sleibler     *                migrate changes made by Christian Marg to current version of plugin \n
68aa11d6a4Sleibler     *
69aa11d6a4Sleibler     *
70aa11d6a4Sleibler     * @param Doku_Event $event
71aa11d6a4Sleibler     * @param mixed $param not defined
72aa11d6a4Sleibler     */
73aa11d6a4Sleibler    public function _ajax_call(&$event, $param) {
74aa11d6a4Sleibler        global $ID, $conf, $lang;
75aa11d6a4Sleibler
76aa11d6a4Sleibler        if($event->data !== 'plugin_todo') {
77aa11d6a4Sleibler            return;
78aa11d6a4Sleibler        }
79aa11d6a4Sleibler        //no other ajax call handlers needed
80aa11d6a4Sleibler        $event->stopPropagation();
81aa11d6a4Sleibler        $event->preventDefault();
82aa11d6a4Sleibler
83aa11d6a4Sleibler        #Variables
84aa11d6a4Sleibler        // by einhirn <marg@rz.tu-clausthal.de> determine checkbox index by using class 'todocheckbox'
85*4afeeeedSRobertWeinmeister        if(isset($_REQUEST['mode'], $_REQUEST['pageid'])) {
86*4afeeeedSRobertWeinmeister            $mode = $_REQUEST['mode'];
87*4afeeeedSRobertWeinmeister            // path = page ID
88*4afeeeedSRobertWeinmeister            $ID = cleanID(urldecode($_REQUEST['pageid']));
89*4afeeeedSRobertWeinmeister        } else {
90*4afeeeedSRobertWeinmeister            return;
91*4afeeeedSRobertWeinmeister        }
92aa11d6a4Sleibler
93*4afeeeedSRobertWeinmeister        if($mode == 'checkbox') {
94aa11d6a4Sleibler            if(isset($_REQUEST['index'], $_REQUEST['checked'], $_REQUEST['pageid'])) {
95aa11d6a4Sleibler                // index = position of occurrence of <input> element (starting with 0 for first element)
96aa11d6a4Sleibler                $index = (int) $_REQUEST['index'];
97aa11d6a4Sleibler                // checked = flag if input is checked means to do is complete (1) or not (0)
98aa11d6a4Sleibler                $checked = (boolean) urldecode($_REQUEST['checked']);
99aa11d6a4Sleibler            } else {
100aa11d6a4Sleibler                return;
101aa11d6a4Sleibler            }
102*4afeeeedSRobertWeinmeister        }
103aa11d6a4Sleibler
104aa11d6a4Sleibler        $date = 0;
105aa11d6a4Sleibler        if(isset($_REQUEST['date'])) $date = (int) $_REQUEST['date'];
106aa11d6a4Sleibler
107aa11d6a4Sleibler        $INFO = pageinfo();
108aa11d6a4Sleibler
109aa11d6a4Sleibler        #Determine Permissions
110aa11d6a4Sleibler        if(auth_quickaclcheck($ID) < AUTH_EDIT) {
111aa11d6a4Sleibler            echo "You do not have permission to edit this file.\nAccess was denied.";
112aa11d6a4Sleibler            return;
113aa11d6a4Sleibler        }
114aa11d6a4Sleibler        // Check, if page is locked
115aa11d6a4Sleibler        if(checklock($ID)) {
116aa11d6a4Sleibler            $locktime = filemtime(wikiLockFN($ID));
117aa11d6a4Sleibler            $expire = dformat($locktime + $conf['locktime']);
118aa11d6a4Sleibler            $min = round(($conf['locktime'] - (time() - $locktime)) / 60);
119aa11d6a4Sleibler
120aa11d6a4Sleibler            $msg = $this->getLang('lockedpage').'
121aa11d6a4Sleibler'.$lang['lockedby'] . ': ' . editorinfo($INFO['locked']) . '
122aa11d6a4Sleibler' . $lang['lockexpire'] . ': ' . $expire . ' (' . $min . ' min)';
123aa11d6a4Sleibler            $this->printJson(array('message' => $msg));
124aa11d6a4Sleibler            return;
125aa11d6a4Sleibler        }
126aa11d6a4Sleibler
127aa11d6a4Sleibler        //conflict check
128aa11d6a4Sleibler        if($date != 0 && $INFO['meta']['date']['modified'] > $date) {
129aa11d6a4Sleibler            $this->printJson(array('message' => $this->getLang('refreshpage')));
130aa11d6a4Sleibler            return;
131aa11d6a4Sleibler        }
132aa11d6a4Sleibler
133aa11d6a4Sleibler        #Retrieve Page Contents
134aa11d6a4Sleibler        $wikitext = rawWiki($ID);
135aa11d6a4Sleibler
136*4afeeeedSRobertWeinmeister        switch($mode) {
137*4afeeeedSRobertWeinmeister            case 'checkbox':
138*4afeeeedSRobertWeinmeister                switch($mode) {
139*4afeeeedSRobertWeinmeister            case 'checkbox':
140aa11d6a4Sleibler                #Determine position of tag
141aa11d6a4Sleibler                        if($index >= 0) {
142aa11d6a4Sleibler                            $index++;
143aa11d6a4Sleibler                            // index is only set on the current page with the todos
144aa11d6a4Sleibler                            // the occurances are counted, untill the index-th input is reached which is updated
145aa11d6a4Sleibler                            $todoTagStartPos = $this->_strnpos($wikitext, '<todo', $index);
146aa11d6a4Sleibler                            $todoTagEndPos = strpos($wikitext, '>', $todoTagStartPos) + 1;
147aa11d6a4Sleibler
148aa11d6a4Sleibler                            if($todoTagEndPos > $todoTagStartPos) {
149aa11d6a4Sleibler                                // @date 20140714 le add todo text to minorchange
150aa11d6a4Sleibler                                $todoTextEndPos = strpos( $wikitext, '</todo', $todoTagEndPos );
151aa11d6a4Sleibler                                $todoText = substr( $wikitext, $todoTagEndPos, $todoTextEndPos-$todoTagEndPos );
152aa11d6a4Sleibler                                // update text
153aa11d6a4Sleibler                                $oldTag = substr($wikitext, $todoTagStartPos, ($todoTagEndPos - $todoTagStartPos));
154aa11d6a4Sleibler                                $newTag = $this->_buildTodoTag($oldTag, $checked);
155aa11d6a4Sleibler                                $wikitext = substr_replace($wikitext, $newTag, $todoTagStartPos, ($todoTagEndPos - $todoTagStartPos));
156aa11d6a4Sleibler
157aa11d6a4Sleibler                                // save Update (Minor)
158aa11d6a4Sleibler                                lock($ID);
159aa11d6a4Sleibler                                // @date 20140714 le add todo text to minorchange, use different message for checked or unchecked
160aa11d6a4Sleibler                                saveWikiText($ID, $wikitext, $this->getLang($checked?'checkboxchange_on':'checkboxchange_off').': '.$todoText, $minoredit = true);
161aa11d6a4Sleibler                                unlock($ID);
162aa11d6a4Sleibler
163aa11d6a4Sleibler                        $return = array(
164aa11d6a4Sleibler                            'date' => @filemtime(wikiFN($ID)),
165aa11d6a4Sleibler                            'succeed' => true
166aa11d6a4Sleibler                        );
167aa11d6a4Sleibler                        $this->printJson($return);
168aa11d6a4Sleibler                    }
169aa11d6a4Sleibler                }
170*4afeeeedSRobertWeinmeister                break;
171*4afeeeedSRobertWeinmeister            case 'uncheckall':
172*4afeeeedSRobertWeinmeister                $newWikitext = preg_replace('/(<todo.*?)(\s+#[^>\s]*)(.*?>|\s.*?<\/todo>)/', '$1$3', $wikitext);
173*4afeeeedSRobertWeinmeister
174*4afeeeedSRobertWeinmeister                lock($ID);
175*4afeeeedSRobertWeinmeister                saveWikiText($ID, $newWikitext, 'Unchecked all ToDos', $minoredit = true);
176*4afeeeedSRobertWeinmeister                unlock($ID);
177*4afeeeedSRobertWeinmeister
178*4afeeeedSRobertWeinmeister                $return = array(
179*4afeeeedSRobertWeinmeister                    'date' => @filemtime(wikiFN($ID)),
180*4afeeeedSRobertWeinmeister                    'succeed' => true
181*4afeeeedSRobertWeinmeister                );
182*4afeeeedSRobertWeinmeister                $this->printJson($return);
183*4afeeeedSRobertWeinmeister                break;
184*4afeeeedSRobertWeinmeister        }
185aa11d6a4Sleibler    }
186aa11d6a4Sleibler
187aa11d6a4Sleibler    /**
188aa11d6a4Sleibler     * Encode and print an arbitrary variable into JSON format
189aa11d6a4Sleibler     *
190aa11d6a4Sleibler     * @param mixed $return
191aa11d6a4Sleibler     */
192aa11d6a4Sleibler    private function printJson($return) {
193aa11d6a4Sleibler        $json = new JSON();
194aa11d6a4Sleibler        echo $json->encode($return);
195aa11d6a4Sleibler    }
196aa11d6a4Sleibler
197aa11d6a4Sleibler    /**
198aa11d6a4Sleibler     * @brief gets current to-do tag and returns a new one depending on checked
199aa11d6a4Sleibler     * @param $todoTag    string current to-do tag e.g. <todo @user>
200aa11d6a4Sleibler     * @param $checked    int check flag (todo completed=1, todo uncompleted=0)
201aa11d6a4Sleibler     * @return string new to-do completed or uncompleted tag e.g. <todo @user #>
202aa11d6a4Sleibler     */
203aa11d6a4Sleibler    private function _buildTodoTag($todoTag, $checked) {
204bd2f9c37Srunout-at        $user = '';
205c34903f3Srunout-at        if($checked == 1) {
206bd2f9c37Srunout-at            if(!empty($_SERVER['REMOTE_USER'])) { $user = $_SERVER['REMOTE_USER']; }
207bd2f9c37Srunout-at            $newTag = preg_replace('/>/', ' #'.$user.':'.date('Y-m-d').'>', $todoTag);
208c34903f3Srunout-at        } else {
209bd2f9c37Srunout-at            $newTag = preg_replace('/[\s]*[#].*>/', '>', $todoTag);
210c34903f3Srunout-at        }
211aa11d6a4Sleibler        return $newTag;
212aa11d6a4Sleibler    }
213aa11d6a4Sleibler
214aa11d6a4Sleibler    /**
215aa11d6a4Sleibler     * Find position of $occurance-th $needle in haystack
216aa11d6a4Sleibler     */
217aa11d6a4Sleibler    private function _strnpos($haystack, $needle, $occurance, $pos = 0) {
218aa11d6a4Sleibler        for($i = 1; $i <= $occurance; $i++) {
219aa11d6a4Sleibler            $pos = strpos($haystack, $needle, $pos) + 1;
220aa11d6a4Sleibler        }
221aa11d6a4Sleibler        return $pos - 1;
222aa11d6a4Sleibler    }
22363314d65SLeo Eibler}