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' 854afeeeedSRobertWeinmeister if(isset($_REQUEST['mode'], $_REQUEST['pageid'])) { 864afeeeedSRobertWeinmeister $mode = $_REQUEST['mode']; 874afeeeedSRobertWeinmeister // path = page ID 884afeeeedSRobertWeinmeister $ID = cleanID(urldecode($_REQUEST['pageid'])); 894afeeeedSRobertWeinmeister } else { 904afeeeedSRobertWeinmeister return; 914afeeeedSRobertWeinmeister } 92aa11d6a4Sleibler 934afeeeedSRobertWeinmeister 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 } 1024afeeeedSRobertWeinmeister } 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 1364afeeeedSRobertWeinmeister switch($mode) { 1374afeeeedSRobertWeinmeister case 'checkbox': 138aa11d6a4Sleibler #Determine position of tag 139aa11d6a4Sleibler if($index >= 0) { 140aa11d6a4Sleibler $index++; 141aa11d6a4Sleibler // index is only set on the current page with the todos 142aa11d6a4Sleibler // the occurances are counted, untill the index-th input is reached which is updated 143aa11d6a4Sleibler $todoTagStartPos = $this->_strnpos($wikitext, '<todo', $index); 144aa11d6a4Sleibler $todoTagEndPos = strpos($wikitext, '>', $todoTagStartPos) + 1; 145aa11d6a4Sleibler 146*db35cc30Seinhirn if($todoTagStartPos!==false && $todoTagEndPos > $todoTagStartPos) { 147aa11d6a4Sleibler // @date 20140714 le add todo text to minorchange 148aa11d6a4Sleibler $todoTextEndPos = strpos( $wikitext, '</todo', $todoTagEndPos ); 149aa11d6a4Sleibler $todoText = substr( $wikitext, $todoTagEndPos, $todoTextEndPos-$todoTagEndPos ); 150aa11d6a4Sleibler // update text 151aa11d6a4Sleibler $oldTag = substr($wikitext, $todoTagStartPos, ($todoTagEndPos - $todoTagStartPos)); 152aa11d6a4Sleibler $newTag = $this->_buildTodoTag($oldTag, $checked); 153aa11d6a4Sleibler $wikitext = substr_replace($wikitext, $newTag, $todoTagStartPos, ($todoTagEndPos - $todoTagStartPos)); 154aa11d6a4Sleibler 155aa11d6a4Sleibler // save Update (Minor) 156aa11d6a4Sleibler lock($ID); 157aa11d6a4Sleibler // @date 20140714 le add todo text to minorchange, use different message for checked or unchecked 158aa11d6a4Sleibler saveWikiText($ID, $wikitext, $this->getLang($checked?'checkboxchange_on':'checkboxchange_off').': '.$todoText, $minoredit = true); 159aa11d6a4Sleibler unlock($ID); 160aa11d6a4Sleibler 161aa11d6a4Sleibler $return = array( 162aa11d6a4Sleibler 'date' => @filemtime(wikiFN($ID)), 163aa11d6a4Sleibler 'succeed' => true 164aa11d6a4Sleibler ); 165aa11d6a4Sleibler $this->printJson($return); 166*db35cc30Seinhirn 167aa11d6a4Sleibler } 168aa11d6a4Sleibler } 1694afeeeedSRobertWeinmeister break; 1704afeeeedSRobertWeinmeister case 'uncheckall': 1714afeeeedSRobertWeinmeister $newWikitext = preg_replace('/(<todo.*?)(\s+#[^>\s]*)(.*?>|\s.*?<\/todo>)/', '$1$3', $wikitext); 1724afeeeedSRobertWeinmeister 1734afeeeedSRobertWeinmeister lock($ID); 1744afeeeedSRobertWeinmeister saveWikiText($ID, $newWikitext, 'Unchecked all ToDos', $minoredit = true); 1754afeeeedSRobertWeinmeister unlock($ID); 1764afeeeedSRobertWeinmeister 1774afeeeedSRobertWeinmeister $return = array( 1784afeeeedSRobertWeinmeister 'date' => @filemtime(wikiFN($ID)), 1794afeeeedSRobertWeinmeister 'succeed' => true 1804afeeeedSRobertWeinmeister ); 1814afeeeedSRobertWeinmeister $this->printJson($return); 1824afeeeedSRobertWeinmeister break; 1834afeeeedSRobertWeinmeister } 184aa11d6a4Sleibler } 185aa11d6a4Sleibler 186aa11d6a4Sleibler /** 187aa11d6a4Sleibler * Encode and print an arbitrary variable into JSON format 188aa11d6a4Sleibler * 189aa11d6a4Sleibler * @param mixed $return 190aa11d6a4Sleibler */ 191aa11d6a4Sleibler private function printJson($return) { 192aa11d6a4Sleibler $json = new JSON(); 193aa11d6a4Sleibler echo $json->encode($return); 194aa11d6a4Sleibler } 195aa11d6a4Sleibler 196aa11d6a4Sleibler /** 197aa11d6a4Sleibler * @brief gets current to-do tag and returns a new one depending on checked 198aa11d6a4Sleibler * @param $todoTag string current to-do tag e.g. <todo @user> 199aa11d6a4Sleibler * @param $checked int check flag (todo completed=1, todo uncompleted=0) 200aa11d6a4Sleibler * @return string new to-do completed or uncompleted tag e.g. <todo @user #> 201aa11d6a4Sleibler */ 202aa11d6a4Sleibler private function _buildTodoTag($todoTag, $checked) { 203bd2f9c37Srunout-at $user = ''; 204c34903f3Srunout-at if($checked == 1) { 205bd2f9c37Srunout-at if(!empty($_SERVER['REMOTE_USER'])) { $user = $_SERVER['REMOTE_USER']; } 206bd2f9c37Srunout-at $newTag = preg_replace('/>/', ' #'.$user.':'.date('Y-m-d').'>', $todoTag); 207c34903f3Srunout-at } else { 208bd2f9c37Srunout-at $newTag = preg_replace('/[\s]*[#].*>/', '>', $todoTag); 209c34903f3Srunout-at } 210aa11d6a4Sleibler return $newTag; 211aa11d6a4Sleibler } 212aa11d6a4Sleibler 213aa11d6a4Sleibler /** 214aa11d6a4Sleibler * Find position of $occurance-th $needle in haystack 215aa11d6a4Sleibler */ 216aa11d6a4Sleibler private function _strnpos($haystack, $needle, $occurance, $pos = 0) { 217aa11d6a4Sleibler for($i = 1; $i <= $occurance; $i++) { 218*db35cc30Seinhirn $pos = strpos($haystack, $needle, $pos); 219*db35cc30Seinhirn 220*db35cc30Seinhirn if ($pos===false) {return false; } 221*db35cc30Seinhirn 222*db35cc30Seinhirn $pos++; 223aa11d6a4Sleibler } 224aa11d6a4Sleibler return $pos - 1; 225aa11d6a4Sleibler } 22663314d65SLeo Eibler} 227