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 /** @var \helper_plugin_ireadit_db $db_helper */ 130 $db_helper = plugin_load('helper', 'approve_db'); 131 $sqlite = $db_helper->getDB(); 132 133 if ($params['approver'] == '$USER$') { 134 $params['approver'] = $INFO['client']; 135 } 136 137 $approver_query = ''; 138 $query_args = [$params['namespace'].'%']; 139 if ($params['approver']) { 140 $approver_query .= " AND page.approver LIKE ?"; 141 $query_args[] = $params['approver']; 142 } 143 144 if ($params['filter']) { 145 $approver_query .= " AND page.page REGEXP ?"; 146 $query_args[] = $params['filter']; 147 } 148 149 //if all 3 states are enabled nothing is filtered 150 if ($params['states'] && count($params['states']) < 3) { 151 if ($this->array_equal(['draft'], $params['states'])) { 152 $approver_query .= " AND revision.ready_for_approval IS NULL AND revision.approved IS NULL"; 153 } elseif ($this->array_equal(['ready_for_approval'], $params['states'])) { 154 $approver_query .= " AND revision.ready_for_approval IS NOT NULL AND revision.approved IS NULL"; 155 } elseif ($this->array_equal(['approved'], $params['states'])) { 156 $approver_query .= " AND revision.approved IS NOT NULL"; 157 } elseif ($this->array_equal(['draft', 'ready_for_approval'], $params['states'])) { 158 $approver_query .= " AND revision.approved IS NULL"; 159 } elseif ($this->array_equal(['draft', 'approved'], $params['states'])) { 160 $approver_query .= " AND (revision.approved IS NOT NULL OR (revision.approved IS NULL AND revision.ready_for_approval IS NULL))"; 161 } elseif ($this->array_equal(['ready_for_approval', 'approved'], $params['states'])) { 162 $approver_query .= " AND (revision.ready_for_approval IS NOT NULL OR revision.approved IS NOT NULL)"; 163 } 164 } 165 166 $q = "SELECT page.page, page.approver, revision.rev, revision.approved, revision.approved_by, 167 revision.ready_for_approval, revision.ready_for_approval_by, 168 LENGTH(page.page) - LENGTH(REPLACE(page.page, ':', '')) AS colons 169 FROM page INNER JOIN revision ON page.page = revision.page 170 WHERE page.hidden = 0 AND revision.current=1 AND page.page LIKE ? ESCAPE '_' 171 $approver_query 172 ORDER BY colons, page.page"; 173 174 $res = $sqlite->query($q, $query_args); 175 $pages = $sqlite->res2arr($res); 176 177 // Output Table 178 $renderer->doc .= '<table><tr>'; 179 $renderer->doc .= '<th>' . $this->getLang('hdr_page') . '</th>'; 180 $renderer->doc .= '<th>' . $this->getLang('hdr_state') . '</th>'; 181 $renderer->doc .= '<th>' . $this->getLang('hdr_updated') . '</th>'; 182 $renderer->doc .= '<th>' . $this->getLang('hdr_approver') . '</th>'; 183 $renderer->doc .= '</tr>'; 184 185 186 $all_approved = 0; 187 $all_approved_ready = 0; 188 $all = 0; 189 190 $curNS = ''; 191 foreach($pages as $page) { 192 $id = $page['page']; 193 $approver = $page['approver']; 194 $rev = $page['rev']; 195 $approved = strtotime($page['approved']); 196 $approved_by = $page['approved_by']; 197 $ready_for_approval = strtotime($page['ready_for_approval']); 198 $ready_for_approval_by = $page['ready_for_approval_by']; 199 200 $pageNS = getNS($id); 201 202 if($pageNS != '' && $pageNS != $curNS) { 203 $curNS = $pageNS; 204 205 $renderer->doc .= '<tr><td colspan="4"><a href="'; 206 $renderer->doc .= wl($curNS); 207 $renderer->doc .= '">'; 208 $renderer->doc .= $curNS; 209 $renderer->doc .= '</a> '; 210 $renderer->doc .= '</td></tr>'; 211 } 212 213 $all += 1; 214 if ($approved) { 215 $class = 'plugin__approve_green'; 216 $state = $this->getLang('approved'); 217 $date = $approved; 218 $by = $approved_by; 219 220 $all_approved += 1; 221 } elseif ($this->getConf('ready_for_approval') && $ready_for_approval) { 222 $class = 'plugin__approve_ready'; 223 $state = $this->getLang('marked_approve_ready'); 224 $date = $ready_for_approval; 225 $by = $ready_for_approval_by; 226 227 $all_approved_ready += 1; 228 } else { 229 $class = 'plugin__approve_red'; 230 $state = $this->getLang('draft'); 231 $date = $rev; 232 $by = p_get_metadata($id, 'last_change user'); 233 } 234 235 $renderer->doc .= '<tr class="'.$class.'">'; 236 $renderer->doc .= '<td><a href="'; 237 $renderer->doc .= wl($id); 238 $renderer->doc .= '">'; 239 if ($conf['useheading'] == '1') { 240 $heading = p_get_first_heading($id); 241 if ($heading != '') { 242 $renderer->doc .= $heading; 243 } else { 244 $renderer->doc .= $id; 245 } 246 } else { 247 $renderer->doc .= $id; 248 } 249 250 $renderer->doc .= '</a></td><td>'; 251 $renderer->doc .= '<strong>'.$state. '</strong> '; 252 253 $user = $auth->getUserData($by); 254 if ($user) { 255 $renderer->doc .= $this->getLang('by'). ' ' . $user['name']; 256 } 257 $renderer->doc .= '</td><td>'; 258 $renderer->doc .= '<a href="' . wl($id) . '">' . dformat($date) . '</a>';; 259 $renderer->doc .= '</td><td>'; 260 if ($approver) { 261 $user = $auth->getUserData($approver); 262 if ($user) { 263 $renderer->doc .= $user['name']; 264 } else { 265 $renderer->doc .= $approver; 266 } 267 } else { 268 $renderer->doc .= '---'; 269 } 270 $renderer->doc .= '</td></tr>'; 271 } 272 273 if ($params['summarize']) { 274 if($this->getConf('ready_for_approval')) { 275 $renderer->doc .= '<tr><td><strong>'; 276 $renderer->doc .= $this->getLang('all_approved_ready'); 277 $renderer->doc .= '</strong></td>'; 278 279 $renderer->doc .= '<td colspan="3">'; 280 $percent = 0; 281 if($all > 0) { 282 $percent = $all_approved_ready * 100 / $all; 283 } 284 $renderer->doc .= $all_approved_ready . ' / ' . $all . sprintf(" (%.0f%%)", $percent); 285 $renderer->doc .= '</td></tr>'; 286 } 287 288 $renderer->doc .= '<tr><td><strong>'; 289 $renderer->doc .= $this->getLang('all_approved'); 290 $renderer->doc .= '</strong></td>'; 291 292 $renderer->doc .= '<td colspan="3">'; 293 $percent = 0; 294 if($all > 0) { 295 $percent = $all_approved * 100 / $all; 296 } 297 $renderer->doc .= $all_approved . ' / ' . $all . sprintf(" (%.0f%%)", $percent); 298 $renderer->doc .= '</td></tr>'; 299 } 300 301 $renderer->doc .= '</table>'; 302 } 303} 304