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}