1<?php 2 3use dokuwiki\plugin\approve\meta\ApproveConst; 4 5// must be run within DokuWiki 6if(!defined('DOKU_INC')) die(); 7 8 9class syntax_plugin_approve_table extends DokuWiki_Syntax_Plugin { 10 11 protected $states = []; 12 13 public function __construct() { 14 $this->states = [$this->getConf('sum approved'), 15 $this->getConf('sum ready for approval'), 16 $this->getConf('sum draft')]; 17 } 18 19 function getType() { 20 return 'substition'; 21 } 22 23 function getSort() { 24 return 20; 25 } 26 27 function PType() { 28 return 'block'; 29 } 30 31 function connectTo($mode) { 32 $this->Lexer->addSpecialPattern('----+ *approve table *-+\n.*?----+', $mode,'plugin_approve_table'); 33 } 34 35 function handle($match, $state, $pos, Doku_Handler $handler){ 36 $lines = explode("\n", $match); 37 array_shift($lines); 38 array_pop($lines); 39 40 $params = []; 41 foreach ($lines as $line) { 42 $pair = explode(':', $line, 2); 43 if (count($pair) < 2) { 44 continue; 45 } 46 $key = trim($pair[0]); 47 $value = trim($pair[1]); 48 if ($key == 'states') { 49 $value = array_map('trim', explode(',', $value)); 50 //normalize 51 $value = array_map('strtolower', $value); 52 $value = array_map('ucfirst', $value); 53 foreach ($value as $state) { 54 if (!in_array($state, $this->states)) { 55 msg('approve plugin: unknown state "'.$state.'" should be: ' . 56 implode(', ', $this->states), -1); 57 return false; 58 } 59 } 60 } elseif($key == 'filter' && preg_match($value, null) === false) { 61 msg('approve plugin: invalid filter regex', -1); 62 return false; 63 } elseif ($key == 'summarize') { 64 $value = $value == '0' ? false : true; 65 } 66 $params[$key] = $value; 67 } 68 return $params; 69 } 70 71 function render($mode, Doku_Renderer $renderer, $params) { 72 global $conf; 73 74 if ($mode != 'xhtml') return false; 75 if ($params === false) return false; 76 77 $defaults = [ 78 'namespace' => '', 79 'filter' => false, 80 'states' => $this->states, 81 'summarize' => true, 82 ]; 83 84 $params = array_replace($defaults, $params); 85 86 $namespace = cleanID(getNS($params['namespace'] . ":*")); 87 88 $pages = $this->_getPagesFromNamespace($namespace, $params['filter'], $params['states']); 89 90 usort($pages, array($this,'_pagesorter')); 91 92 // Output Table 93 $renderer->doc .= '<table><tr>'; 94 $renderer->doc .= '<th>' . $this->getLang('hdr_page') . '</th>'; 95 $renderer->doc .= '<th>' . $this->getLang('hdr_state') . '</th>'; 96 $renderer->doc .= '<th>' . $this->getLang('hdr_updated') . '</th>'; 97 $renderer->doc .= '</tr>'; 98 99 100 $all_approved = 0; 101 $all_approved_ready = 0; 102 $all = 0; 103 104 $working_ns = null; 105 foreach($pages as $page) { 106 // $page: 0 -> pagename, 1 -> true -> approved else false, 2 -> last changed date 107 $this_ns = getNS($page[0]); 108 109 if($this_ns != $working_ns) { 110 $name_ns = $this_ns; 111 if($this_ns == '') { $name_ns = 'root'; } 112 $renderer->doc .= '<tr><td colspan="3"><a href="'; 113 $renderer->doc .= wl($this_ns . ':' . $this->getConf('start')); 114 $renderer->doc .= '">'; 115 $renderer->doc .= $name_ns; 116 $renderer->doc .= '</a> '; 117 $renderer->doc .= '</td></tr>'; 118 $working_ns = $this_ns; 119 } 120 121 $updated = '<a href="' . wl($page[0]) . '">' . dformat($page[2]) . '</a>'; 122 123 $class = 'plugin__approve_red'; 124 $state = $this->getLang('draft'); 125 $all += 1; 126 127 if ($page[1] === 'approved') { 128 $class = 'plugin__approve_green'; 129 $state = $this->getLang('approved'); 130 $all_approved += 1; 131 } elseif ($page[1] === 'ready for approval' && $this->getConf('ready_for_approval') === 1) { 132 $class = 'plugin__approve_ready'; 133 $state = $this->getLang('marked_approve_ready'); 134 $all_approved_ready += 1; 135 } 136 137 $renderer->doc .= '<tr class="'.$class.'">'; 138 $renderer->doc .= '<td><a href="'; 139 $renderer->doc .= wl($page[0]); 140 $renderer->doc .= '">'; 141 if ($conf['useheading'] === '1') { 142 $heading = p_get_first_heading($page[0]); 143 if ($heading != '') { 144 $renderer->doc .= $heading; 145 } else { 146 $renderer->doc .= $page[0]; 147 } 148 149 } else { 150 $renderer->doc .= $page[0]; 151 } 152 153 $renderer->doc .= '</a></td><td>'; 154 $renderer->doc .= '<strong>'.$state. '</strong> '. $this->getLang('by'). ' ' . $page[4]; 155 $renderer->doc .= '</td><td>'; 156 $renderer->doc .= $updated; 157 $renderer->doc .= '</td></tr>'; 158 } 159 160 if ($params['summarize']) { 161 if($this->getConf('ready_for_approval') === 1) { 162 $renderer->doc .= '<tr><td><strong>'; 163 $renderer->doc .= $this->getLang('all_approved_ready'); 164 $renderer->doc .= '</strong></td>'; 165 166 $renderer->doc .= '<td colspan="2">'; 167 $percent = 0; 168 if($all > 0) { 169 $percent = $all_approved_ready * 100 / $all; 170 } 171 $renderer->doc .= $all_approved_ready . ' / ' . $all . sprintf(" (%.0f%%)", $percent); 172 $renderer->doc .= '</td></tr>'; 173 } 174 175 $renderer->doc .= '<tr><td><strong>'; 176 $renderer->doc .= $this->getLang('all_approved'); 177 $renderer->doc .= '</strong></td>'; 178 179 $renderer->doc .= '<td colspan="2">'; 180 $percent = 0; 181 if($all > 0) { 182 $percent = $all_approved * 100 / $all; 183 } 184 $renderer->doc .= $all_approved . ' / ' . $all . sprintf(" (%.0f%%)", $percent); 185 $renderer->doc .= '</td></tr>'; 186 } 187 188 $renderer->doc .= '</table>'; 189 return true; 190 } 191 192 function _search_helper(&$data, $base, $file, $type, $lvl, $opts) { 193 global $lang; 194 195 $ns = $opts[0]; 196 $invalid_ns = $opts[1]; 197 $page_regex = $opts[2]; 198 $states = $opts[3]; 199 200 if ($type == 'd') { 201 return true; 202 } 203 204 if (!preg_match('#\.txt$#', $file)) { 205 return false; 206 } 207 208 $id = pathID($ns . $file); 209 if (!empty($invalid_ns) && $this->hlp->in_namespace($invalid_ns, $id)) { 210 return false; 211 } 212 213 //check page_regex 214 if ($page_regex && !preg_match($page_regex, $id)) { 215 return false; 216 } 217 218 //check states 219 220 221 $meta = p_get_metadata($id); 222 223 224 //check states 225 $sum = $meta['last_change']['sum']; 226 if ($sum == $this->getConf('sum approved') && !in_array($this->getConf('sum approved'), $states)) { 227 return false; 228 } 229 230 if ($sum == $this->getConf('sum ready for approval') && 231 !in_array($this->getConf('sum ready for approval'), $states)) { 232 return false; 233 } 234 235 if ($sum != $this->getConf('sum approved') && 236 $sum != $this->getConf('sum ready for approval') && 237 !in_array($this->getConf('sum draft'), $states)) { 238 return false; 239 } 240 241 $date = $meta['date']['modified']; 242 if (isset($meta['last_change']) && $meta['last_change']['sum'] === $this->getConf('sum approved')) { 243 $approved = 'approved'; 244 } elseif (isset($meta['last_change']) && $meta['last_change']['sum'] === $this->getConf('sum ready for approval')) { 245 $approved = 'ready for approval'; 246 } else { 247 $approved = 'not approved'; 248 } 249 250 if (isset($meta['last_change'])) { 251 $user = $meta['last_change']['user']; 252 253 if (isset($meta['contributor'][$user])) { 254 $full_name = $meta['contributor'][$user]; 255 } else { 256 $full_name = $meta['creator']; 257 } 258 } else { 259 $user = ''; 260 $full_name = '('.$lang['external_edit'].')'; 261 } 262 263 264 $data[] = array($id, $approved, $date, $user, $full_name); 265 266 return false; 267 } 268 269 function _getPagesFromNamespace($namespace, $page_regex=false, $states=[]) { 270 global $conf; 271 $dir = $conf['datadir'] . '/' . str_replace(':', '/', $namespace); 272 $pages = array(); 273 search($pages, $dir, array($this,'_search_helper'), 274 array($namespace, $this->getConf('no_apr_namespaces'), $page_regex, $states)); 275 276 return $pages; 277 } 278 279 280 281 /** 282 * Custom sort callback 283 */ 284 function _pagesorter($a, $b){ 285 $ac = explode(':',$a[0]); 286 $bc = explode(':',$b[0]); 287 $an = count($ac); 288 $bn = count($bc); 289 290 // Same number of elements, can just string sort 291 if($an == $bn) { return strcmp($a[0], $b[0]); } 292 293 // For each level: 294 // If this is not the last element in either list: 295 // same -> continue 296 // otherwise strcmp 297 // If this is the last element in either list, it wins 298 $n = 0; 299 while(true) { 300 if($n + 1 == $an) { return -1; } 301 if($n + 1 == $bn) { return 1; } 302 $s = strcmp($ac[$n], $bc[$n]); 303 if($s != 0) { return $s; } 304 $n += 1; 305 } 306 } 307 308} 309