1<?php 2 3// must be run within DokuWiki 4if(!defined('DOKU_INC')) die(); 5 6 7class syntax_plugin_approve_table extends DokuWiki_Syntax_Plugin { 8 9 protected $states = ['approved', 'draft', 'ready_for_approval']; 10 11 function getType() { 12 return 'substition'; 13 } 14 15 function getSort() { 16 return 20; 17 } 18 19 function PType() { 20 return 'block'; 21 } 22 23 function connectTo($mode) { 24 $this->Lexer->addSpecialPattern('----+ *approve table *-+\n.*?----+', $mode,'plugin_approve_table'); 25 } 26 27 function handle($match, $state, $pos, Doku_Handler $handler){ 28 $lines = explode("\n", $match); 29 array_shift($lines); 30 array_pop($lines); 31 32 $params = [ 33 'namespace' => '', 34 'filter' => false, 35 'states' => [], 36 'summarize' => true, 37 'approver' => null 38 ]; 39 40 foreach ($lines as $line) { 41 $pair = explode(':', $line, 2); 42 if (count($pair) < 2) { 43 continue; 44 } 45 $key = trim($pair[0]); 46 $value = trim($pair[1]); 47 if ($key == 'states') { 48 $value = array_map('trim', explode(',', $value)); 49 //normalize 50 $value = array_map('strtolower', $value); 51 foreach ($value as $state) { 52 if (!in_array($state, $this->states)) { 53 msg('approve plugin: unknown state "'.$state.'" should be: ' . 54 implode(', ', $this->states), -1); 55 return false; 56 } 57 } 58 } elseif($key == 'filter') { 59 $value = trim($value, '/'); 60 if (preg_match('/' . $value . '/', null) === false) { 61 msg('approve plugin: invalid filter regex', -1); 62 return false; 63 } 64 } elseif ($key == 'summarize') { 65 $value = $value == '0' ? false : true; 66 } elseif ($key == 'namespace') { 67 $value = trim(cleanID($value), ':'); 68 } 69 $params[$key] = $value; 70 } 71 return $params; 72 } 73 74 /** 75 * Render xhtml output or metadata 76 * 77 * @param string $mode Renderer mode (supported modes: xhtml) 78 * @param Doku_Renderer $renderer The renderer 79 * @param array $data The data from the handler() function 80 * 81 * @return bool If rendering was successful. 82 */ 83 84 public function render($mode, Doku_Renderer $renderer, $data) 85 { 86 $method = 'render' . ucfirst($mode); 87 if (method_exists($this, $method)) { 88 call_user_func([$this, $method], $renderer, $data); 89 return true; 90 } 91 return false; 92 } 93 94 /** 95 * Render metadata 96 * 97 * @param Doku_Renderer $renderer The renderer 98 * @param array $data The data from the handler() function 99 */ 100 public function renderMetadata(Doku_Renderer $renderer, $params) 101 { 102 $plugin_name = $this->getPluginName(); 103 $renderer->meta['plugin'][$plugin_name] = []; 104 105 if ($params['approver'] == '$USER$') { 106 $renderer->meta['plugin'][$plugin_name]['dynamic_approver'] = true; 107 } 108 109 $renderer->meta['plugin'][$plugin_name]['approve_table'] = true; 110 } 111 112 protected function array_equal($a, $b) { 113 return ( 114 is_array($a) 115 && is_array($b) 116 && count($a) == count($b) 117 && array_diff($a, $b) === array_diff($b, $a) 118 ); 119 } 120 121 public function renderXhtml(Doku_Renderer $renderer, $params) 122 { 123 global $INFO; 124 125 global $conf; 126 /** @var DokuWiki_Auth_Plugin $auth */ 127 global $auth; 128 129 try { 130 /** @var \helper_plugin_approve_db $db_helper */ 131 $db_helper = plugin_load('helper', 'approve_db'); 132 $sqlite = $db_helper->getDB(); 133 } catch (Exception $e) { 134 msg($e->getMessage(), -1); 135 return; 136 } 137 138 if ($params['approver'] == '$USER$') { 139 $params['approver'] = $INFO['client']; 140 } 141 142 $approver_query = ''; 143 $query_args = [$params['namespace'].'*']; 144 if ($params['approver']) { 145 $approver_query .= " AND page.approver LIKE ?"; 146 $query_args[] = $params['approver']; 147 } 148 149 if ($params['filter']) { 150 $approver_query .= " AND page.page REGEXP ?"; 151 $query_args[] = $params['filter']; 152 } 153 154 //if all 3 states are enabled nothing is filtered 155 if ($params['states'] && count($params['states']) < 3) { 156 if ($this->array_equal(['draft'], $params['states'])) { 157 $approver_query .= " AND revision.ready_for_approval IS NULL AND revision.approved IS NULL"; 158 } elseif ($this->array_equal(['ready_for_approval'], $params['states'])) { 159 $approver_query .= " AND revision.ready_for_approval IS NOT NULL AND revision.approved IS NULL"; 160 } elseif ($this->array_equal(['approved'], $params['states'])) { 161 $approver_query .= " AND revision.approved IS NOT NULL"; 162 } elseif ($this->array_equal(['draft', 'ready_for_approval'], $params['states'])) { 163 $approver_query .= " AND revision.approved IS NULL"; 164 } elseif ($this->array_equal(['draft', 'approved'], $params['states'])) { 165 $approver_query .= " AND (revision.approved IS NOT NULL OR (revision.approved IS NULL AND revision.ready_for_approval IS NULL))"; 166 } elseif ($this->array_equal(['ready_for_approval', 'approved'], $params['states'])) { 167 $approver_query .= " AND (revision.ready_for_approval IS NOT NULL OR revision.approved IS NOT NULL)"; 168 } 169 } 170 171 $q = "SELECT page.page, page.approver, revision.rev, revision.approved, revision.approved_by, 172 revision.ready_for_approval, revision.ready_for_approval_by, 173 LENGTH(page.page) - LENGTH(REPLACE(page.page, ':', '')) AS colons 174 FROM page INNER JOIN revision ON page.page = revision.page 175 WHERE page.hidden = 0 AND revision.current=1 AND page.page GLOB ? 176 $approver_query 177 ORDER BY colons, page.page"; 178 179 $res = $sqlite->query($q, $query_args); 180 $pages = $sqlite->res2arr($res); 181 182 // Output Table 183 $renderer->doc .= '<table><tr>'; 184 $renderer->doc .= '<th>' . $this->getLang('hdr_page') . '</th>'; 185 $renderer->doc .= '<th>' . $this->getLang('hdr_state') . '</th>'; 186 $renderer->doc .= '<th>' . $this->getLang('hdr_updated') . '</th>'; 187 $renderer->doc .= '<th>' . $this->getLang('hdr_approver') . '</th>'; 188 $renderer->doc .= '</tr>'; 189 190 191 $all_approved = 0; 192 $all_approved_ready = 0; 193 $all = 0; 194 195 $curNS = ''; 196 foreach($pages as $page) { 197 $id = $page['page']; 198 $approver = $page['approver']; 199 $rev = $page['rev']; 200 $approved = strtotime($page['approved']); 201 $approved_by = $page['approved_by']; 202 $ready_for_approval = strtotime($page['ready_for_approval']); 203 $ready_for_approval_by = $page['ready_for_approval_by']; 204 205 $pageNS = getNS($id); 206 207 if($pageNS != '' && $pageNS != $curNS) { 208 $curNS = $pageNS; 209 210 $renderer->doc .= '<tr><td colspan="4"><a href="'; 211 $renderer->doc .= wl($curNS); 212 $renderer->doc .= '">'; 213 $renderer->doc .= $curNS; 214 $renderer->doc .= '</a> '; 215 $renderer->doc .= '</td></tr>'; 216 } 217 218 $all += 1; 219 if ($approved) { 220 $class = 'plugin__approve_green'; 221 $state = $this->getLang('approved'); 222 $date = $approved; 223 $by = $approved_by; 224 225 $all_approved += 1; 226 } elseif ($this->getConf('ready_for_approval') && $ready_for_approval) { 227 $class = 'plugin__approve_ready'; 228 $state = $this->getLang('marked_approve_ready'); 229 $date = $ready_for_approval; 230 $by = $ready_for_approval_by; 231 232 $all_approved_ready += 1; 233 } else { 234 $class = 'plugin__approve_red'; 235 $state = $this->getLang('draft'); 236 $date = $rev; 237 $by = p_get_metadata($id, 'last_change user'); 238 } 239 240 $renderer->doc .= '<tr class="'.$class.'">'; 241 $renderer->doc .= '<td><a href="'; 242 $renderer->doc .= wl($id); 243 $renderer->doc .= '">'; 244 if ($conf['useheading'] == '1') { 245 $heading = p_get_first_heading($id); 246 if ($heading != '') { 247 $renderer->doc .= $heading; 248 } else { 249 $renderer->doc .= $id; 250 } 251 } else { 252 $renderer->doc .= $id; 253 } 254 255 $renderer->doc .= '</a></td><td>'; 256 $renderer->doc .= '<strong>'.$state. '</strong> '; 257 258 $user = $auth->getUserData($by); 259 if ($user) { 260 $renderer->doc .= $this->getLang('by'). ' ' . $user['name']; 261 } 262 $renderer->doc .= '</td><td>'; 263 $renderer->doc .= '<a href="' . wl($id) . '">' . dformat($date) . '</a>';; 264 $renderer->doc .= '</td><td>'; 265 if ($approver) { 266 $user = $auth->getUserData($approver); 267 if ($user) { 268 $renderer->doc .= $user['name']; 269 } else { 270 $renderer->doc .= $approver; 271 } 272 } else { 273 $renderer->doc .= '---'; 274 } 275 $renderer->doc .= '</td></tr>'; 276 } 277 278 if ($params['summarize']) { 279 if($this->getConf('ready_for_approval')) { 280 $renderer->doc .= '<tr><td><strong>'; 281 $renderer->doc .= $this->getLang('all_approved_ready'); 282 $renderer->doc .= '</strong></td>'; 283 284 $renderer->doc .= '<td colspan="3">'; 285 $percent = 0; 286 if($all > 0) { 287 $percent = $all_approved_ready * 100 / $all; 288 } 289 $renderer->doc .= $all_approved_ready . ' / ' . $all . sprintf(" (%.0f%%)", $percent); 290 $renderer->doc .= '</td></tr>'; 291 } 292 293 $renderer->doc .= '<tr><td><strong>'; 294 $renderer->doc .= $this->getLang('all_approved'); 295 $renderer->doc .= '</strong></td>'; 296 297 $renderer->doc .= '<td colspan="3">'; 298 $percent = 0; 299 if($all > 0) { 300 $percent = $all_approved * 100 / $all; 301 } 302 $renderer->doc .= $all_approved . ' / ' . $all . sprintf(" (%.0f%%)", $percent); 303 $renderer->doc .= '</td></tr>'; 304 } 305 306 $renderer->doc .= '</table>'; 307 } 308} 309