* @date 20130405 Leo Eibler \n * replace old sack() method with new jQuery method and use post instead of get \n * @date 20130408 Leo Eibler \n * remove getInfo() call because it's done by plugin.info.txt (since dokuwiki 2009-12-25 Lemming) */ if(!defined('DOKU_INC')) die(); /** * Class action_plugin_todo registers actions */ class action_plugin_todo extends DokuWiki_Action_Plugin { /** * Register the eventhandlers */ public function register(Doku_Event_Handler $controller) { $controller->register_hook('TOOLBAR_DEFINE', 'AFTER', $this, 'insert_button', array()); $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, '_ajax_call', array()); } /** * Inserts the toolbar button */ public function insert_button(&$event, $param) { $event->data[] = array( 'type' => 'format', 'title' => $this->getLang('qb_todobutton'), 'icon' => '../../plugins/todo/todo.png', // key 't' is already used for going to top of page, bug #76 // 'key' => 't', 'open' => '', 'close' => '', 'block' => false, ); } /** * Handles ajax requests for to do plugin * * @brief This method is called by ajax if the user clicks on the to-do checkbox or the to-do text. * It sets the to-do state to completed or reset it to open. * * POST Parameters: * index int the position of the occurrence of the input element (starting with 0 for first element/to-do) * checked int should the to-do set to completed (1) or to open (0) * path string id/path/name of the page * * @date 20140317 Leo Eibler \n * use todo content as change description \n * @date 20131008 Gerrit Uitslag \n * move ajax.php to action.php, added lock and conflict checks and improved saving * @date 20130405 Leo Eibler \n * replace old sack() method with new jQuery method and use post instead of get \n * @date 20130407 Leo Eibler \n * add user assignment for todos \n * @date 20130408 Christian Marg \n * change only the clicked to-do item instead of all items with the same text \n * origVal is not used anymore, we use the index (occurrence) of input element \n * @date 20130408 Leo Eibler \n * migrate changes made by Christian Marg to current version of plugin \n * * * @param Doku_Event $event * @param mixed $param not defined */ public function _ajax_call(&$event, $param) { global $ID, $conf, $lang; if($event->data !== 'plugin_todo') { return; } //no other ajax call handlers needed $event->stopPropagation(); $event->preventDefault(); #Variables // by einhirn determine checkbox index by using class 'todocheckbox' if(isset($_REQUEST['mode'], $_REQUEST['pageid'])) { $mode = $_REQUEST['mode']; // path = page ID $ID = cleanID(urldecode($_REQUEST['pageid'])); } else { return; } if($mode == 'checkbox') { if(isset($_REQUEST['index'], $_REQUEST['checked'], $_REQUEST['pageid'])) { // index = position of occurrence of element (starting with 0 for first element) $index = (int) $_REQUEST['index']; // checked = flag if input is checked means to do is complete (1) or not (0) $checked = (boolean) urldecode($_REQUEST['checked']); } else { return; } } $date = 0; if(isset($_REQUEST['date'])) $date = (int) $_REQUEST['date']; $INFO = pageinfo(); #Determine Permissions if(auth_quickaclcheck($ID) < AUTH_EDIT) { echo "You do not have permission to edit this file.\nAccess was denied."; return; } // Check, if page is locked if(checklock($ID)) { $locktime = filemtime(wikiLockFN($ID)); $expire = dformat($locktime + $conf['locktime']); $min = round(($conf['locktime'] - (time() - $locktime)) / 60); $msg = $this->getLang('lockedpage').' '.$lang['lockedby'] . ': ' . editorinfo($INFO['locked']) . ' ' . $lang['lockexpire'] . ': ' . $expire . ' (' . $min . ' min)'; $this->printJson(array('message' => $msg)); return; } //conflict check if($date != 0 && $INFO['meta']['date']['modified'] > $date) { $this->printJson(array('message' => $this->getLang('refreshpage'))); return; } #Retrieve Page Contents $wikitext = rawWiki($ID); switch($mode) { case 'checkbox': #Determine position of tag if($index >= 0) { $index++; // index is only set on the current page with the todos // the occurances are counted, untill the index-th input is reached which is updated $todoTagStartPos = $this->_strnpos($wikitext, '', $todoTagStartPos) + 1; if($todoTagStartPos!==false && $todoTagEndPos > $todoTagStartPos) { // @date 20140714 le add todo text to minorchange $todoTextEndPos = strpos( $wikitext, '_buildTodoTag($oldTag, $checked); $wikitext = substr_replace($wikitext, $newTag, $todoTagStartPos, ($todoTagEndPos - $todoTagStartPos)); // save Update (Minor) lock($ID); // @date 20140714 le add todo text to minorchange, use different message for checked or unchecked saveWikiText($ID, $wikitext, $this->getLang($checked?'checkboxchange_on':'checkboxchange_off').': '.$todoText, $minoredit = true); unlock($ID); $return = array( 'date' => @filemtime(wikiFN($ID)), 'succeed' => true ); $this->printJson($return); } } break; case 'uncheckall': $newWikitext = preg_replace('/(\s]*)(.*?>|\s.*?<\/todo>)/', '$1$3', $wikitext); lock($ID); saveWikiText($ID, $newWikitext, 'Unchecked all ToDos', $minoredit = true); unlock($ID); $return = array( 'date' => @filemtime(wikiFN($ID)), 'succeed' => true ); $this->printJson($return); break; } } /** * Encode and print an arbitrary variable into JSON format * * @param mixed $return */ private function printJson($return) { $json = new JSON(); echo $json->encode($return); } /** * @brief gets current to-do tag and returns a new one depending on checked * @param $todoTag string current to-do tag e.g. * @param $checked int check flag (todo completed=1, todo uncompleted=0) * @return string new to-do completed or uncompleted tag e.g. */ private function _buildTodoTag($todoTag, $checked) { $user = ''; if($checked == 1) { if(!empty($_SERVER['REMOTE_USER'])) { $user = $_SERVER['REMOTE_USER']; } $newTag = preg_replace('/>/', ' #'.$user.':'.date('Y-m-d').'>', $todoTag); } else { $newTag = preg_replace('/[\s]*[#].*>/', '>', $todoTag); } return $newTag; } /** * Find position of $occurance-th $needle in haystack */ private function _strnpos($haystack, $needle, $occurance, $pos = 0) { for($i = 1; $i <= $occurance; $i++) { $pos = strpos($haystack, $needle, $pos); if ($pos===false) {return false; } $pos++; } return $pos - 1; } }