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(&$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', 37 'open' => '<todo>', 38 'close' => '</todo>', 39 'block' => false, 40 ); 41 } 42 43 /** 44 * Handles ajax requests for to do plugin 45 * 46 * @brief This method is called by ajax if the user clicks on the to-do checkbox or the to-do text. 47 * It sets the to-do state to completed or reset it to open. 48 * 49 * POST Parameters: 50 * index int the position of the occurrence of the input element (starting with 0 for first element/to-do) 51 * checked int should the to-do set to completed (1) or to open (0) 52 * path string id/path/name of the page 53 * 54 * @date 20131008 Gerrit Uitslag <klapinklapin@gmail.com> \n 55 * move ajax.php to action.php, added lock and conflict checks and improved saving 56 * @date 20130405 Leo Eibler <dokuwiki@sprossenwanne.at> \n 57 * replace old sack() method with new jQuery method and use post instead of get \n 58 * @date 20130407 Leo Eibler <dokuwiki@sprossenwanne.at> \n 59 * add user assignment for todos \n 60 * @date 20130408 Christian Marg <marg@rz.tu-clausthal.de> \n 61 * change only the clicked to-do item instead of all items with the same text \n 62 * origVal is not used anymore, we use the index (occurrence) of input element \n 63 * @date 20130408 Leo Eibler <dokuwiki@sprossenwanne.at> \n 64 * migrate changes made by Christian Marg to current version of plugin \n 65 * 66 * 67 * @param Doku_Event $event 68 * @param mixed $param not defined 69 */ 70 public function _ajax_call(&$event, $param) { 71 global $ID, $DATE; 72 73 if($event->data !== 'plugin_todo') { 74 return; 75 } 76 //no other ajax call handlers needed 77 $event->stopPropagation(); 78 $event->preventDefault(); 79 80 #Variables 81 // by einhirn <marg@rz.tu-clausthal.de> determine checkbox index by using class 'todocheckbox' 82 83 if(isset($_REQUEST['index'], $_REQUEST['checked'], $_REQUEST['path'])) { 84 // index = position of occurrence of <input> element (starting with 0 for first element) 85 $index = (int) $_REQUEST['index']; 86 // checked = flag if input is checked means to do is complete (1) or not (0) 87 $checked = urldecode($_REQUEST['checked']); 88 // path = page ID (name) 89 $ID = cleanID(urldecode($_REQUEST['path'])); 90 } else { 91 return; 92 } 93 // origVal = urlencoded original value (in the case this is called by dokuwiki searchpattern plugin rendered page) 94 $origVal = ''; 95 if(isset($_REQUEST['origVal'])) $origVal = urldecode($_REQUEST['origVal']); 96 97 $date = 0; 98 if(isset($_REQUEST['date'])) $date = (int) $_REQUEST['date']; 99 100 $INFO = pageinfo(); 101 102 #Determine Permissions 103 if(auth_quickaclcheck($ID) < AUTH_EDIT) { 104 echo "You do not have permission to edit this file.\nAccess was denied."; 105 return; 106 } 107 // Check, if page is locked 108 if(checklock($ID)) { 109 $this->printJson(array('message' => 'The page is currently locked.')); 110 } 111 112 //conflict check 113 if($date != 0 && $INFO['meta']['date']['modified'] > $date) { 114 $this->printJson(array('message' => 'A newer version of this page is available, refresh your page before trying again.')); 115 return; 116 } 117 118 #Retrieve Page Contents 119 $wikitext = rawWiki($ID); 120 121 #Determine position of tag 122 $contentChanged = false; 123 124 if($index >= 0) { 125 $index++; 126 // index is only set on the current page with the todos 127 // the occurances are counted, untill the index-th input is reached which is updated 128 $todoTagStartPos = strnpos($wikitext, '<todo', $index); 129 $todoTagEndPos = strpos($wikitext, '>', $todoTagStartPos) + 1; 130 if($todoTagEndPos > $todoTagStartPos) { 131 $contentChanged = true; 132 } 133 } else { 134 // this will happen if we are on a dokuwiki searchpattern plugin summary page 135 $checkedpattern = $checked ? '' : '*#[^>]'; 136 $pattern = '/(<todo[^#>]' . $checkedpattern . '*>(' . preg_quote($origVal) . '<\/todo[\W]*?>))/'; 137 138 $x = preg_match_all($pattern, $wikitext, $spMatches, PREG_OFFSET_CAPTURE); 139 if($x && isset($spMatches[0][0])) { 140 // yes, we found matches and index is in a valid range 141 $todoTagStartPos = $spMatches[1][0][1]; 142 $todoTagEndPos = $spMatches[2][0][1]; 143 144 $contentChanged = true; 145 } 146 } 147 148 // Modify content 149 if($contentChanged) { 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 saveWikiText($ID, $wikitext, 'Checkbox Change', $minoredit = true); 158 unlock($ID); 159 160 $return = array('date' => @filemtime(wikiFN($ID))); 161 $this->printJson($return); 162 } 163 164 } 165 166 private function printJson($return) { 167 $json = new JSON(); 168 echo $json->encode($return); 169 } 170 171 /** 172 * @brief gets current to-do tag and returns a new one depending on checked 173 * @param $todoTag string current to-do tag e.g. <todo @user> 174 * @param $checked int check flag (todo completed=1, todo uncompleted=0) 175 * @return string new to-do completed or uncompleted tag e.g. <todo @user #> 176 */ 177 private function _buildTodoTag($todoTag, $checked) { 178 $x = preg_match('%<todo([^>]*)>%i', $todoTag, $matches); 179 $newTag = '<todo'; 180 if($x) { 181 if(($userPos = strpos($matches[1], '@')) !== false) { 182 $submatch = substr($todoTag, $userPos); 183 $x = preg_match('%@([-.\w]+)%i', $submatch, $matchinguser); 184 if($x) { 185 $newTag .= ' @' . $matchinguser[1]; 186 } 187 } 188 } 189 if($checked == 1) { 190 $newTag .= ' #'; 191 } 192 $newTag .= '>'; 193 return $newTag; 194 } 195 196 /** 197 * @brief Convert a string to a regex so it can be used in PHP "preg_match" function 198 * from dokuwiki searchpattern plugin 199 */ 200 private function _todoStr2regex($str) { 201 $regex = ''; //init 202 for($i = 0; $i < strlen($str); $i++) { //for each char in the string 203 if(!ctype_alnum($str[$i])) { //if char is not alpha-numeric 204 $regex = $regex . '\\'; //escape it with a backslash 205 } 206 $regex = $regex . $str[$i]; //compose regex 207 } 208 return $regex; //return 209 } 210 211} 212 213if(!function_exists('strnpos')) { 214 /** 215 * Find position of $occurance-th $needle in haystack 216 */ 217 function strnpos($haystack, $needle, $occurance, $pos = 0) { 218 for($i = 1; $i <= $occurance; $i++) { 219 $pos = strpos($haystack, $needle, $pos) + 1; 220 } 221 return $pos - 1; 222 } 223}