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 20130405 Leo Eibler <dokuwiki@sprossenwanne.at> \n 55 * replace old sack() method with new jQuery method and use post instead of get \n 56 * @date 20130407 Leo Eibler <dokuwiki@sprossenwanne.at> \n 57 * add user assignment for todos \n 58 * @date 20130408 Christian Marg <marg@rz.tu-clausthal.de> \n 59 * change only the clicked to-do item instead of all items with the same text \n 60 * origVal is not used anymore, we use the index (occurrence) of input element \n 61 * @date 20130408 Leo Eibler <dokuwiki@sprossenwanne.at> \n 62 * migrate changes made by Christian Marg to current version of plugin \n 63 * 64 * 65 * @param Doku_Event $event 66 * @param mixed $param not defined 67 */ 68 public function _ajax_call(&$event, $param) { 69 global $ID; 70 71 if($event->data !== 'plugin_todo') { 72 return; 73 } 74 //no other ajax call handlers needed 75 $event->stopPropagation(); 76 $event->preventDefault(); 77 78 #Variables 79 // by einhirn <marg@rz.tu-clausthal.de> determine checkbox index by using class 'todocheckbox' 80 81 if(isset($_REQUEST['index'], $_REQUEST['checked'], $_REQUEST['path'])) { 82 // index = position of occurrence of <input> element (starting with 0 for first element) 83 $index = urldecode($_REQUEST['index']); 84 // checked = flag if input is checked means to do is complete (1) or not (0) 85 $checked = urldecode($_REQUEST['checked']); 86 // path = page ID (name) 87 $ID = cleanID(urldecode($_REQUEST['path'])); 88 } else { 89 return; 90 } 91 $origVal = ''; 92 if(isset($_REQUEST['origVal'])) { 93 // origVal = urlencoded original value (in the case this is called by dokuwiki searchpattern plugin rendered page) 94 $origVal = urldecode($_REQUEST['origVal']); 95 } 96 97 $INFO = pageinfo(); //FIXME is this same as global $INFO;? 98 $fileName = $INFO['filepath']; 99 100 #Determine Permissions 101 if(auth_quickaclcheck($ID) < AUTH_EDIT) { 102 echo "You do not have permission to edit this file.\nAccess was denied."; 103 return; 104 } 105 106 #Retrieve File Contents 107 $newContents = file_get_contents($fileName); 108 109 $contentChanged = false; 110 #Modify Contents 111 112 if($index >= 0) { 113 $index++; 114 // no origVal so we count all todos with the method from Christian Marg 115 // this will happen if we are on the current page with the todos 116 $todoPos = strnpos($newContents, '<todo', $index); 117 $todoTextPost = strpos($newContents, '>', $todoPos) + 1; 118 if($todoTextPost > $todoPos) { 119 $todoTag = substr($newContents, $todoPos, $todoTextPost - $todoPos); 120 $newTag = $this->_todoProcessTag($todoTag, $checked); 121 $newContents = substr_replace($newContents, $newTag, $todoPos, ($todoTextPost - $todoPos)); 122 $contentChanged = true; 123 } 124 } else { 125 // this will happen if we are on a dokuwiki searchpattern plugin summary page 126 if($checked) { 127 $pattern = '/(<todo[^#>]*>(' . $this->_todoStr2regex($origVal) . '<\/todo[\W]*?>))/'; 128 } else { 129 $pattern = '/(<todo[^#>]*#[^>]*>(' . $this->_todoStr2regex($origVal) . '<\/todo[\W]*?>))/'; 130 } 131 $x = preg_match_all($pattern, $newContents, $spMatches, PREG_OFFSET_CAPTURE); 132 if($x && isset($spMatches[0][0])) { 133 // yes, we found matches and index is in a valid range 134 $todoPos = $spMatches[1][0][1]; 135 $todoTextPost = $spMatches[2][0][1]; 136 $todoTag = substr($newContents, $todoPos, $todoTextPost - $todoPos); 137 $newTag = $this->_todoProcessTag($todoTag, $checked); 138 $newContents = substr_replace($newContents, $newTag, $todoPos, ($todoTextPost - $todoPos)); 139 $contentChanged = true; 140 } 141 } 142 143 if($contentChanged) { 144 #Save Update (Minor) 145 io_writeWikiPage($fileName, $newContents, $ID, ''); 146 addLogEntry(saveOldRevision($ID), $ID, DOKU_CHANGE_TYPE_MINOR_EDIT, "Checkbox Change", ''); 147 } 148 149 } 150 151#(Possible) Alternative Method 152//Retrieve mtime from file 153//Load Data 154//Modify Data 155//Save Data 156//Replace new mtime with previous one 157 158 /** 159 * @brief gets current to-do tag and returns a new one depending on checked 160 * @param $todoTag string current to-do tag e.g. <todo @user> 161 * @param $checked int check flag (todo completed=1, todo uncompleted=0) 162 * @return string new to-do completed or uncompleted tag e.g. <todo @user #> 163 */ 164 private function _todoProcessTag($todoTag, $checked) { 165 $x = preg_match('%<todo([^>]*)>%i', $todoTag, $pregmatches); 166 $newTag = '<todo'; 167 if($x) { 168 if(($uPos = strpos($pregmatches[1], '@')) !== false) { 169 $match2 = substr($todoTag, $uPos); 170 $x = preg_match('%@([-.\w]+)%i', $match2, $pregmatches); 171 if($x) { 172 $todo_user = $pregmatches[1]; 173 $newTag .= ' @' . $todo_user; 174 } 175 } 176 } 177 if($checked == 1) { 178 $newTag .= ' #'; 179 } 180 $newTag .= '>'; 181 return $newTag; 182 } 183 184 /** 185 * @brief Convert a string to a regex so it can be used in PHP "preg_match" function 186 * from dokuwiki searchpattern plugin 187 */ 188 private function _todoStr2regex($str) { 189 $regex = ''; //init 190 for($i = 0; $i < strlen($str); $i++) { //for each char in the string 191 if(!ctype_alnum($str[$i])) { //if char is not alpha-numeric 192 $regex = $regex . '\\'; //escape it with a backslash 193 } 194 $regex = $regex . $str[$i]; //compose regex 195 } 196 return $regex; //return 197 } 198 199} 200 201 202if(!function_exists('strnpos')) { 203 /** 204 * Find position of $occurance-th $needle in haystack 205 */ 206 function strnpos($haystack, $needle, $occurance, $pos = 0) { 207 for($i = 1; $i <= $occurance; $i++) { 208 $pos = strpos($haystack, $needle, $pos) + 1; 209 } 210 return $pos - 1; 211 } 212}