xref: /plugin/todo/action.php (revision aa11d6a46e34fdcba785525ca93431f9f30b4c89)
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
11*aa11d6a4Sleibler *                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/**
16*aa11d6a4Sleibler * Class action_plugin_todo registers actions
1763314d65SLeo Eibler */
18*aa11d6a4Sleiblerclass action_plugin_todo extends DokuWiki_Action_Plugin {
1963314d65SLeo Eibler
2063314d65SLeo Eibler    /**
2163314d65SLeo Eibler     * Register the eventhandlers
2263314d65SLeo Eibler     */
23*aa11d6a4Sleibler    public function register(Doku_Event_Handler $controller) {
2463314d65SLeo Eibler        $controller->register_hook('TOOLBAR_DEFINE', 'AFTER', $this, 'insert_button', array());
25*aa11d6a4Sleibler        $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     */
31*aa11d6a4Sleibler    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',
3663314d65SLeo Eibler            'key' => 't',
3763314d65SLeo Eibler            'open' => '<todo>',
3863314d65SLeo Eibler            'close' => '</todo>',
3916a9e697SLeo Eibler            'block' => false,
4063314d65SLeo Eibler        );
4163314d65SLeo Eibler    }
4263314d65SLeo Eibler
43*aa11d6a4Sleibler    /**
44*aa11d6a4Sleibler     * Handles ajax requests for to do plugin
45*aa11d6a4Sleibler     *
46*aa11d6a4Sleibler     * @brief This method is called by ajax if the user clicks on the to-do checkbox or the to-do text.
47*aa11d6a4Sleibler     * It sets the to-do state to completed or reset it to open.
48*aa11d6a4Sleibler     *
49*aa11d6a4Sleibler     * POST Parameters:
50*aa11d6a4Sleibler     *   index    int the position of the occurrence of the input element (starting with 0 for first element/to-do)
51*aa11d6a4Sleibler     *   checked    int should the to-do set to completed (1) or to open (0)
52*aa11d6a4Sleibler     *   path    string id/path/name of the page
53*aa11d6a4Sleibler     *
54*aa11d6a4Sleibler     * @date 20140317 Leo Eibler <dokuwiki@sprossenwanne.at> \n
55*aa11d6a4Sleibler     *                use todo content as change description \n
56*aa11d6a4Sleibler     * @date 20131008 Gerrit Uitslag <klapinklapin@gmail.com> \n
57*aa11d6a4Sleibler     *                move ajax.php to action.php, added lock and conflict checks and improved saving
58*aa11d6a4Sleibler     * @date 20130405 Leo Eibler <dokuwiki@sprossenwanne.at> \n
59*aa11d6a4Sleibler     *                replace old sack() method with new jQuery method and use post instead of get \n
60*aa11d6a4Sleibler     * @date 20130407 Leo Eibler <dokuwiki@sprossenwanne.at> \n
61*aa11d6a4Sleibler     *                add user assignment for todos \n
62*aa11d6a4Sleibler     * @date 20130408 Christian Marg <marg@rz.tu-clausthal.de> \n
63*aa11d6a4Sleibler     *                change only the clicked to-do item instead of all items with the same text \n
64*aa11d6a4Sleibler     *                origVal is not used anymore, we use the index (occurrence) of input element \n
65*aa11d6a4Sleibler     * @date 20130408 Leo Eibler <dokuwiki@sprossenwanne.at> \n
66*aa11d6a4Sleibler     *                migrate changes made by Christian Marg to current version of plugin \n
67*aa11d6a4Sleibler     *
68*aa11d6a4Sleibler     *
69*aa11d6a4Sleibler     * @param Doku_Event $event
70*aa11d6a4Sleibler     * @param mixed $param not defined
71*aa11d6a4Sleibler     */
72*aa11d6a4Sleibler    public function _ajax_call(&$event, $param) {
73*aa11d6a4Sleibler        global $ID, $conf, $lang;
74*aa11d6a4Sleibler
75*aa11d6a4Sleibler        if($event->data !== 'plugin_todo') {
76*aa11d6a4Sleibler            return;
77*aa11d6a4Sleibler        }
78*aa11d6a4Sleibler        //no other ajax call handlers needed
79*aa11d6a4Sleibler        $event->stopPropagation();
80*aa11d6a4Sleibler        $event->preventDefault();
81*aa11d6a4Sleibler
82*aa11d6a4Sleibler        #Variables
83*aa11d6a4Sleibler        // by einhirn <marg@rz.tu-clausthal.de> determine checkbox index by using class 'todocheckbox'
84*aa11d6a4Sleibler
85*aa11d6a4Sleibler        if(isset($_REQUEST['index'], $_REQUEST['checked'], $_REQUEST['pageid'])) {
86*aa11d6a4Sleibler            // index = position of occurrence of <input> element (starting with 0 for first element)
87*aa11d6a4Sleibler            $index = (int) $_REQUEST['index'];
88*aa11d6a4Sleibler            // checked = flag if input is checked means to do is complete (1) or not (0)
89*aa11d6a4Sleibler            $checked = (boolean) urldecode($_REQUEST['checked']);
90*aa11d6a4Sleibler            // path = page ID
91*aa11d6a4Sleibler            $ID = cleanID(urldecode($_REQUEST['pageid']));
92*aa11d6a4Sleibler        } else {
93*aa11d6a4Sleibler            return;
94*aa11d6a4Sleibler        }
95*aa11d6a4Sleibler
96*aa11d6a4Sleibler        $date = 0;
97*aa11d6a4Sleibler        if(isset($_REQUEST['date'])) $date = (int) $_REQUEST['date'];
98*aa11d6a4Sleibler
99*aa11d6a4Sleibler        $INFO = pageinfo();
100*aa11d6a4Sleibler
101*aa11d6a4Sleibler        #Determine Permissions
102*aa11d6a4Sleibler        if(auth_quickaclcheck($ID) < AUTH_EDIT) {
103*aa11d6a4Sleibler            echo "You do not have permission to edit this file.\nAccess was denied.";
104*aa11d6a4Sleibler            return;
105*aa11d6a4Sleibler        }
106*aa11d6a4Sleibler        // Check, if page is locked
107*aa11d6a4Sleibler        if(checklock($ID)) {
108*aa11d6a4Sleibler            $locktime = filemtime(wikiLockFN($ID));
109*aa11d6a4Sleibler            $expire = dformat($locktime + $conf['locktime']);
110*aa11d6a4Sleibler            $min = round(($conf['locktime'] - (time() - $locktime)) / 60);
111*aa11d6a4Sleibler
112*aa11d6a4Sleibler            $msg = $this->getLang('lockedpage').'
113*aa11d6a4Sleibler'.$lang['lockedby'] . ': ' . editorinfo($INFO['locked']) . '
114*aa11d6a4Sleibler' . $lang['lockexpire'] . ': ' . $expire . ' (' . $min . ' min)';
115*aa11d6a4Sleibler            $this->printJson(array('message' => $msg));
116*aa11d6a4Sleibler            return;
117*aa11d6a4Sleibler        }
118*aa11d6a4Sleibler
119*aa11d6a4Sleibler        //conflict check
120*aa11d6a4Sleibler        if($date != 0 && $INFO['meta']['date']['modified'] > $date) {
121*aa11d6a4Sleibler            $this->printJson(array('message' => $this->getLang('refreshpage')));
122*aa11d6a4Sleibler            return;
123*aa11d6a4Sleibler        }
124*aa11d6a4Sleibler
125*aa11d6a4Sleibler        #Retrieve Page Contents
126*aa11d6a4Sleibler        $wikitext = rawWiki($ID);
127*aa11d6a4Sleibler
128*aa11d6a4Sleibler        #Determine position of tag
129*aa11d6a4Sleibler        if($index >= 0) {
130*aa11d6a4Sleibler            $index++;
131*aa11d6a4Sleibler            // index is only set on the current page with the todos
132*aa11d6a4Sleibler            // the occurances are counted, untill the index-th input is reached which is updated
133*aa11d6a4Sleibler            $todoTagStartPos = $this->_strnpos($wikitext, '<todo', $index);
134*aa11d6a4Sleibler            $todoTagEndPos = strpos($wikitext, '>', $todoTagStartPos) + 1;
135*aa11d6a4Sleibler
136*aa11d6a4Sleibler            if($todoTagEndPos > $todoTagStartPos) {
137*aa11d6a4Sleibler				// @date 20140714 le add todo text to minorchange
138*aa11d6a4Sleibler				$todoTextEndPos = strpos( $wikitext, '</todo', $todoTagEndPos );
139*aa11d6a4Sleibler				$todoText = substr( $wikitext, $todoTagEndPos, $todoTextEndPos-$todoTagEndPos );
140*aa11d6a4Sleibler                // update text
141*aa11d6a4Sleibler                $oldTag = substr($wikitext, $todoTagStartPos, ($todoTagEndPos - $todoTagStartPos));
142*aa11d6a4Sleibler                $newTag = $this->_buildTodoTag($oldTag, $checked);
143*aa11d6a4Sleibler                $wikitext = substr_replace($wikitext, $newTag, $todoTagStartPos, ($todoTagEndPos - $todoTagStartPos));
144*aa11d6a4Sleibler
145*aa11d6a4Sleibler                // save Update (Minor)
146*aa11d6a4Sleibler                lock($ID);
147*aa11d6a4Sleibler				// @date 20140714 le add todo text to minorchange, use different message for checked or unchecked
148*aa11d6a4Sleibler                saveWikiText($ID, $wikitext, $this->getLang($checked?'checkboxchange_on':'checkboxchange_off').': '.$todoText, $minoredit = true);
149*aa11d6a4Sleibler                unlock($ID);
150*aa11d6a4Sleibler
151*aa11d6a4Sleibler                $return = array(
152*aa11d6a4Sleibler                    'date' => @filemtime(wikiFN($ID)),
153*aa11d6a4Sleibler                    'succeed' => true
154*aa11d6a4Sleibler                );
155*aa11d6a4Sleibler                $this->printJson($return);
156*aa11d6a4Sleibler            }
157*aa11d6a4Sleibler        }
158*aa11d6a4Sleibler    }
159*aa11d6a4Sleibler
160*aa11d6a4Sleibler    /**
161*aa11d6a4Sleibler     * Encode and print an arbitrary variable into JSON format
162*aa11d6a4Sleibler     *
163*aa11d6a4Sleibler     * @param mixed $return
164*aa11d6a4Sleibler     */
165*aa11d6a4Sleibler    private function printJson($return) {
166*aa11d6a4Sleibler        $json = new JSON();
167*aa11d6a4Sleibler        echo $json->encode($return);
168*aa11d6a4Sleibler    }
169*aa11d6a4Sleibler
170*aa11d6a4Sleibler    /**
171*aa11d6a4Sleibler     * @brief gets current to-do tag and returns a new one depending on checked
172*aa11d6a4Sleibler     * @param $todoTag    string current to-do tag e.g. <todo @user>
173*aa11d6a4Sleibler     * @param $checked    int check flag (todo completed=1, todo uncompleted=0)
174*aa11d6a4Sleibler     * @return string new to-do completed or uncompleted tag e.g. <todo @user #>
175*aa11d6a4Sleibler     */
176*aa11d6a4Sleibler    private function _buildTodoTag($todoTag, $checked) {
177*aa11d6a4Sleibler        $x = preg_match('%<todo([^>]*)>%i', $todoTag, $matches);
178*aa11d6a4Sleibler        $newTag = '<todo';
179*aa11d6a4Sleibler        if($x) {
180*aa11d6a4Sleibler            if(($userPos = strpos($matches[1], '@')) !== false) {
181*aa11d6a4Sleibler                $submatch = substr($todoTag, $userPos);
182*aa11d6a4Sleibler                $x = preg_match('%@([-.\w]+)%i', $submatch, $matchinguser);
183*aa11d6a4Sleibler                if($x) {
184*aa11d6a4Sleibler                    $newTag .= ' @' . $matchinguser[1];
185*aa11d6a4Sleibler                }
186*aa11d6a4Sleibler            }
187*aa11d6a4Sleibler        }
188*aa11d6a4Sleibler        if($checked == 1) {
189*aa11d6a4Sleibler            $newTag .= ' #';
190*aa11d6a4Sleibler        }
191*aa11d6a4Sleibler        $newTag .= '>';
192*aa11d6a4Sleibler        return $newTag;
193*aa11d6a4Sleibler    }
194*aa11d6a4Sleibler
195*aa11d6a4Sleibler    /**
196*aa11d6a4Sleibler     * Find position of $occurance-th $needle in haystack
197*aa11d6a4Sleibler     */
198*aa11d6a4Sleibler    private function _strnpos($haystack, $needle, $occurance, $pos = 0) {
199*aa11d6a4Sleibler        for($i = 1; $i <= $occurance; $i++) {
200*aa11d6a4Sleibler            $pos = strpos($haystack, $needle, $pos) + 1;
201*aa11d6a4Sleibler        }
202*aa11d6a4Sleibler        return $pos - 1;
203*aa11d6a4Sleibler    }
20463314d65SLeo Eibler}