1<?php 2/** 3 * DokuWiki Plugin todo (Syntax Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 */ 7 8// must be run within Dokuwiki 9if(!defined('DOKU_INC')) die(); 10 11/** 12 * Class syntax_plugin_todo_list 13 */ 14class syntax_plugin_todo_list extends DokuWiki_Syntax_Plugin { 15 16 /** @var syntax_plugin_todo_todo */ 17 protected $todo; 18 19 /** 20 * constructor 21 */ 22 public function __construct() { 23 $this->todo = new syntax_plugin_todo_todo(); 24 } 25 26 /** 27 * @return string Syntax mode type 28 */ 29 public function getType() { 30 return 'substition'; 31 } 32 33 /** 34 * @return string Paragraph type 35 */ 36 public function getPType() { 37 return 'block'; 38 } 39 40 /** 41 * @return int Sort order - Low numbers go before high numbers 42 */ 43 public function getSort() { 44 return 250; 45 } 46 47 /** 48 * Connect lookup pattern to lexer. 49 * 50 * @param string $mode Parser mode 51 */ 52 public function connectTo($mode) { 53 $this->Lexer->addSpecialPattern('~~TODOLIST[^~]*~~', $mode, 'plugin_todo_list'); 54 } 55 56 /** 57 * Handle matches of the todo syntax 58 * 59 * @param string $match The match of the syntax 60 * @param int $state The state of the handler 61 * @param int $pos The position in the document 62 * @param Doku_Handler $handler The handler 63 * @return array Data for the renderer 64 */ 65 public function handle($match, $state, $pos, Doku_Handler &$handler) { 66 67 $options = substr($match, 10, -2); // strip markup 68 $options = explode(' ', $options); 69 $data = array( 70 'completed' => 'all', 71 'assigned' => 'all', 72 'pos' => $pos 73 ); 74 $allowedvalues = array('yes', 'no'); 75 foreach($options as $option) { 76 @list($key, $value) = explode(':', $option, 2); 77 switch($key) { 78 case 'completed': 79 if(in_array($value, $allowedvalues)) { 80 $data['completed'] = ($value == 'yes'); 81 } 82 break; 83 case 'assigned': 84 if(in_array($value, $allowedvalues)) { 85 $data['assigned'] = ($value == 'yes'); 86 break; 87 } 88 //assigned? 89 $data['assigned'] = explode(',', $value); //TODO check escaping 90 $data['assigned'] = array_map( 91 function ($user) { 92 return ltrim($user, '@'); 93 }, $data['assigned'] 94 ); 95 break; 96 } 97 } 98 return $data; 99 } 100 101 /** 102 * Render xhtml output or metadata 103 * 104 * @param string $mode Renderer mode (supported modes: xhtml) 105 * @param Doku_Renderer $renderer The renderer 106 * @param array $data The data from the handler() function 107 * @return bool If rendering was successful. 108 */ 109 public function render($mode, Doku_Renderer &$renderer, $data) { 110 global $conf; 111 112 if($mode != 'xhtml') return false; 113 /** @var Doku_Renderer_xhtml $renderer */ 114 115 $opts['pattern'] = '/<todo([^>]*)>(.*)<\/todo[\W]*?>/'; //all todos in a wiki page 116 //TODO check if storing subpatterns doesn't cost too much resources 117 118 // search(&$data, $base, $func, $opts,$dir='',$lvl=1,$sort='natural') 119 search($todopages, $conf['datadir'], array($this, 'search_todos'), $opts); //browse wiki pages with callback to search_pattern 120 121 $todopages = $this->_filterpages($todopages, $data); 122 123 $this->_htmlTodoTable($renderer, $todopages); 124 125 return true; 126 } 127 128 /** 129 * Custom search callback 130 * 131 * This function is called for every found file or 132 * directory. When a directory is given to the function it has to 133 * decide if this directory should be traversed (true) or not (false). 134 * Return values for files are ignored 135 * 136 * All functions should check the ACL for document READ rights 137 * namespaces (directories) are NOT checked (when sneaky_index is 0) as this 138 * would break the recursion (You can have an nonreadable dir over a readable 139 * one deeper nested) also make sure to check the file type (for example 140 * in case of lockfiles). 141 * 142 * @param array &$data - Reference to the result data structure 143 * @param string $base - Base usually $conf['datadir'] 144 * @param string $file - current file or directory relative to $base 145 * @param string $type - Type either 'd' for directory or 'f' for file 146 * @param int $lvl - Current recursion depht 147 * @param array $opts - option array as given to search() 148 * @return bool if this directory should be traversed (true) or not (false). Return values for files are ignored. 149 */ 150 public function search_todos(&$data, $base, $file, $type, $lvl, $opts) { 151 $item['id'] = pathID($file); //get current file ID 152 153 //we do nothing with directories 154 if($type == 'd') return true; 155 156 //only search txt files 157 if(substr($file, -4) != '.txt') return true; 158 159 //check ACL 160 if(auth_quickaclcheck($item['id']) < AUTH_READ) return false; 161 162 $wikitext = rawWiki($item['id']); //get wiki text 163 164 $item['count'] = preg_match_all($opts['pattern'], $wikitext, $matches); //count how many times appears the pattern 165 if(!empty($item['count'])) { //if it appears at least once 166 $item['matches'] = $matches; 167 $data[] = $item; 168 } 169 return true; 170 } 171 172 /** 173 * filter the pages 174 * 175 * @param $todopages array pages with all todoitems 176 * @param $data array listing parameters 177 * @return array filtered pages 178 */ 179 private function _filterpages($todopages, $data) { 180 $pages = array(); 181 foreach($todopages as $page) { 182 $todos = array(); 183 foreach($page['matches'][0] as $todoindex => $todomatches) { 184 list($checked, $todouser) = $this->todo->_parseTodoArgs($page['matches'][1][$todoindex]); 185 $todotitle = trim($page['matches'][2][$todoindex]); 186 187 if( 188 ( 189 //completed? 190 $data['completed'] === 'all' 191 OR $data['completed'] === $checked //yes or no 192 ) AND ( 193 //assigned? 194 $data['assigned'] === 'all' 195 OR (is_bool($data['assigned']) && $data['assigned'] == $todouser) //yes or no 196 OR (is_array($data['assigned']) && in_array($todouser, $data['assigned'])) //one of requested users? 197 ) 198 ) { 199 $todos[] = array($todotitle, $todoindex, $todouser, $checked); 200 } 201 } 202 if(count($todos) > 0) { 203 $pages[] = array('id' => $page['id'], 'todos' => $todos); 204 } 205 } 206 return $pages; 207 } 208 209 /** 210 * Create html for table with todos 211 * 212 * @param Doku_Renderer_xhtml $R 213 * @param array $todopages 214 */ 215 private function _htmlTodoTable($R, $todopages) { 216 $R->table_open(); 217 foreach($todopages as $page) { 218 $R->tablerow_open(); 219 $R->tableheader_open(); 220 $R->internallink($page['id'], $page['id']); 221 $R->tableheader_close(); 222 $R->tablerow_close(); 223 foreach($page['todos'] as $todo) { 224 $R->tablerow_open(); 225 $R->tablecell_open(); 226 $R->doc .= $this->todo->_createTodoItem($R, $todo[0], $todo[1], $todo[2], $todo[3], $page['id']); 227 $R->tablecell_close(); 228 $R->tablerow_close(); 229 } 230 } 231 $R->table_close(); 232 } 233} 234