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