1<?php 2 3if(!defined('DOKU_INC')) die(); 4 5class action_plugin_approve_approve extends DokuWiki_Action_Plugin { 6 /** 7 * @param Doku_Event_Handler $controller 8 */ 9 public function register(Doku_Event_Handler $controller) { 10 $controller->register_hook('TPL_ACT_RENDER', 'AFTER', $this, 'handle_diff_accept'); 11 $controller->register_hook('HTML_SHOWREV_OUTPUT', 'BEFORE', $this, 'handle_showrev'); 12 $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_approve'); 13 $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_mark_ready_for_approval'); 14 $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_viewer'); 15 $controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'handle_display_banner'); 16 $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'AFTER', $this, 'handle_pagesave_after'); 17 } 18 19 /** 20 * @param Doku_Event $event 21 */ 22 public function handle_diff_accept(Doku_Event $event) { 23 global $INFO; 24 25 try { 26 /** @var \helper_plugin_approve_db $db_helper */ 27 $db_helper = plugin_load('helper', 'approve_db'); 28 $sqlite = $db_helper->getDB(); 29 } catch (Exception $e) { 30 msg($e->getMessage(), -1); 31 return; 32 } 33 /** @var helper_plugin_approve $helper */ 34 $helper = plugin_load('helper', 'approve'); 35 36 if (!$helper->use_approve_here($sqlite, $INFO['id'])) return; 37 38 if ($event->data == 'diff' && isset($_GET['approve'])) { 39 $href = wl($INFO['id'], ['approve' => 'approve']); 40 ptln('<a href="' . $href . '">'.$this->getLang('approve').'</a>'); 41 } 42 43 if ($this->getConf('ready_for_approval') && $event->data == 'diff' && isset($_GET['ready_for_approval'])) { 44 $href = wl($INFO['id'], ['ready_for_approval' => 'ready_for_approval']); 45 ptln('<a href="' . $href . '">'.$this->getLang('approve_ready').'</a>'); 46 } 47 } 48 49 /** 50 * @param Doku_Event $event 51 */ 52 public function handle_showrev(Doku_Event $event) { 53 global $INFO; 54 55 try { 56 /** @var \helper_plugin_approve_db $db_helper */ 57 $db_helper = plugin_load('helper', 'approve_db'); 58 $sqlite = $db_helper->getDB(); 59 } catch (Exception $e) { 60 msg($e->getMessage(), -1); 61 return; 62 } 63 /** @var helper_plugin_approve $helper */ 64 $helper = plugin_load('helper', 'approve'); 65 66 if (!$helper->use_approve_here($sqlite, $INFO['id'])) return; 67 68 $last_approved_rev = $helper->find_last_approved($sqlite, $INFO['id']); 69 if ($last_approved_rev == $INFO['rev']) { 70 $event->preventDefault(); 71 } 72 } 73 74 /** 75 * @param Doku_Event $event 76 */ 77 public function handle_approve(Doku_Event $event) { 78 global $INFO; 79 80 try { 81 /** @var \helper_plugin_approve_db $db_helper */ 82 $db_helper = plugin_load('helper', 'approve_db'); 83 $sqlite = $db_helper->getDB(); 84 } catch (Exception $e) { 85 msg($e->getMessage(), -1); 86 return; 87 } 88 /** @var helper_plugin_approve $helper */ 89 $helper = plugin_load('helper', 'approve'); 90 91 if ($event->data != 'show') return; 92 if (!isset($_GET['approve'])) return; 93 if (!$helper->use_approve_here($sqlite, $INFO['id'], $approver)) return; 94 if (!$helper->client_can_approve($INFO['id'], $approver)) return; 95 96 $res = $sqlite->query('SELECT MAX(version)+1 FROM revision 97 WHERE page=?', $INFO['id']); 98 $next_version = $sqlite->res2single($res); 99 if (!$next_version) { 100 $next_version = 1; 101 } 102 //approved IS NULL prevents from overriding already approved page 103 $sqlite->query('UPDATE revision 104 SET approved=?, approved_by=?, version=? 105 WHERE page=? AND current=1 AND approved IS NULL', 106 date('c'), $INFO['client'], $next_version, $INFO['id']); 107 108 header('Location: ' . wl($INFO['id'])); 109 } 110 111 /** 112 * @param Doku_Event $event 113 */ 114 public function handle_mark_ready_for_approval(Doku_Event $event) { 115 global $INFO; 116 117 try { 118 /** @var \helper_plugin_approve_db $db_helper */ 119 $db_helper = plugin_load('helper', 'approve_db'); 120 $sqlite = $db_helper->getDB(); 121 } catch (Exception $e) { 122 msg($e->getMessage(), -1); 123 return; 124 } 125 /** @var helper_plugin_approve $helper */ 126 $helper = plugin_load('helper', 'approve'); 127 128 if ($event->data != 'show') return; 129 if (!isset($_GET['ready_for_approval'])) return; 130 if (!$helper->use_approve_here($sqlite, $INFO['id'])) return; 131 if (!$helper->client_can_mark_ready_for_approval($INFO['id'])) return; 132 133 $sqlite->query('UPDATE revision SET ready_for_approval=?, ready_for_approval_by=? 134 WHERE page=? AND current=1 AND ready_for_approval IS NULL', 135 date('c'), $INFO['client'], $INFO['id']); 136 137 header('Location: ' . wl($INFO['id'])); 138 } 139 140 /** 141 * Redirect to newest approved page for user that don't have EDIT permission. 142 * 143 * @param Doku_Event $event 144 */ 145 public function handle_viewer(Doku_Event $event) { 146 global $INFO; 147 148 try { 149 /** @var \helper_plugin_approve_db $db_helper */ 150 $db_helper = plugin_load('helper', 'approve_db'); 151 $sqlite = $db_helper->getDB(); 152 } catch (Exception $e) { 153 msg($e->getMessage(), -1); 154 return; 155 } 156 /** @var helper_plugin_approve $helper */ 157 $helper = plugin_load('helper', 'approve'); 158 159 if ($event->data != 'show') return; 160 //apply only to current page 161 if ($INFO['rev'] != 0) return; 162 if (!$helper->use_approve_here($sqlite, $INFO['id'], $approver)) return; 163 if ($helper->client_can_see_drafts($INFO['id'], $approver)) return; 164 165 $last_approved_rev = $helper->find_last_approved($sqlite, $INFO['id']); 166 //no page is approved 167 if (!$last_approved_rev) return; 168 169 $last_change_date = @filemtime(wikiFN($INFO['id'])); 170 //current page is approved 171 if ($last_approved_rev == $last_change_date) return; 172 173 header("Location: " . wl($INFO['id'], ['rev' => $last_approved_rev], false, '&')); 174 } 175 176 /** 177 * @param Doku_Event $event 178 */ 179 public function handle_display_banner(Doku_Event $event) { 180 global $INFO, $ID; 181 182 /* Return true if banner should not be displayed for users with or below read only permission. */ 183 if (auth_quickaclcheck($ID) <= AUTH_READ && !$this->getConf('display_banner_for_readonly')) { 184 return true; 185 } 186 187 /* Not returned - rendering the banner */ 188 try { 189 /** @var \helper_plugin_approve_db $db_helper */ 190 $db_helper = plugin_load('helper', 'approve_db'); 191 $sqlite = $db_helper->getDB(); 192 } catch (Exception $e) { 193 msg($e->getMessage(), -1); 194 return; 195 } 196 /** @var helper_plugin_approve $helper */ 197 $helper = plugin_load('helper', 'approve'); 198 199 if ($event->data != 'show') return; 200 if (!$INFO['exists']) return; 201 if (!$helper->use_approve_here($sqlite, $INFO['id'], $approver)) return; 202 203// $last_change_date = p_get_metadata($INFO['id'], 'last_change date'); 204 $last_change_date = @filemtime(wikiFN($INFO['id'])); 205 $rev = !$INFO['rev'] ? $last_change_date : $INFO['rev']; 206 207 208 $res = $sqlite->query('SELECT ready_for_approval, ready_for_approval_by, 209 approved, approved_by, version 210 FROM revision 211 WHERE page=? AND rev=?', $INFO['id'], $rev); 212 213 $approve = $sqlite->res_fetch_assoc($res); 214 $last_approved_rev = $helper->find_last_approved($sqlite, $INFO['id']); 215 216 $classes = []; 217 if ($this->getConf('prettyprint')) { 218 $classes[] = 'plugin__approve_noprint'; 219 } 220 221 if ($approve['approved'] && $rev == $last_approved_rev) { 222 $classes[] = 'plugin__approve_approved'; 223 } elseif ($approve['approved']) { 224 $classes[] = 'plugin__approve_old_approved'; 225 } elseif ($this->getConf('ready_for_approval') && $approve['ready_for_approval']) { 226 $classes[] = 'plugin__approve_ready'; 227 } else { 228 $classes[] = 'plugin__approve_draft'; 229 } 230 231 ptln('<div id="plugin__approve" class="' . implode(' ', $classes) . '">'); 232 233// tpl_pageinfo(); 234// ptln(' | '); 235 236 if ($approve['approved']) { 237 ptln('<strong>'.$this->getLang('approved').'</strong>'); 238 ptln(' ' . dformat(strtotime($approve['approved']))); 239 240 if($this->getConf('banner_long')) { 241 ptln(' ' . $this->getLang('by') . ' ' . userlink($approve['approved_by'], true)); 242 ptln(' (' . $this->getLang('version') . ': ' . $approve['version'] . ')'); 243 } 244 245 //not the newest page 246 if ($rev != $last_change_date) { 247 $res = $sqlite->query('SELECT rev, current FROM revision 248 WHERE page=? AND approved IS NOT NULL 249 ORDER BY rev DESC LIMIT 1', $INFO['id']); 250 251 $last_approve = $sqlite->res_fetch_assoc($res); 252 253 //we can see drafts 254 if ($helper->client_can_see_drafts($INFO['id'], $approver)) { 255 ptln('<a href="' . wl($INFO['id']) . '">'); 256 ptln($this->getLang($last_approve['current'] ? 'newest_approved' : 'newest_draft')); 257 ptln('</a>'); 258 //we cannot see link to draft but there is some newer approved version 259 } elseif ($last_approve['rev'] != $rev) { 260 $urlParameters = []; 261 if (!$last_approve['current']) { 262 $urlParameters['rev'] = $last_approve['rev']; 263 } 264 ptln('<a href="' . wl($INFO['id'], $urlParameters) . '">'); 265 ptln($this->getLang('newest_approved')); 266 ptln('</a>'); 267 } 268 } 269 270 } else { 271 if ($this->getConf('ready_for_approval') && $approve['ready_for_approval']) { 272 ptln('<strong>'.$this->getLang('marked_approve_ready').'</strong>'); 273 ptln(' ' . dformat(strtotime($approve['ready_for_approval']))); 274 ptln(' ' . $this->getLang('by') . ' ' . userlink($approve['ready_for_approval_by'], true)); 275 } else { 276 ptln('<strong>'.$this->getLang('draft').'</strong>'); 277 } 278 279 280 $res = $sqlite->query('SELECT rev, current FROM revision 281 WHERE page=? AND approved IS NOT NULL 282 ORDER BY rev DESC LIMIT 1', $INFO['id']); 283 284 $last_approve = $sqlite->res_fetch_assoc($res); 285 286 287 //not exists approve for current page 288 if (!$last_approve) { 289 //not the newest page 290 if ($rev != $last_change_date) { 291 ptln('<a href="'.wl($INFO['id']).'">'); 292 ptln($this->getLang('newest_draft')); 293 ptln('</a>'); 294 } 295 } else { 296 $urlParameters = []; 297 if (!$last_approve['current']) { 298 $urlParameters['rev'] = $last_approve['rev']; 299 } 300 ptln('<a href="' . wl($INFO['id'], $urlParameters) . '">'); 301 ptln($this->getLang('newest_approved')); 302 ptln('</a>'); 303 } 304 305 //we are in current page 306 if ($rev == $last_change_date) { 307 308 //compare with the last approved page or 0 if there is no approved versions 309 $last_approved_rev = 0; 310 if (isset($last_approve['rev'])) { 311 $last_approved_rev = $last_approve['rev']; 312 } 313 314 if ($this->getConf('ready_for_approval') && 315 $helper->client_can_mark_ready_for_approval($INFO['id']) && 316 !$approve['ready_for_approval']) { 317 318 $urlParameters = [ 319 'rev' => $last_approved_rev, 320 'do' => 'diff', 321 'ready_for_approval' => 'ready_for_approval' 322 ]; 323 ptln(' | <a href="'.wl($INFO['id'], $urlParameters).'">'); 324 ptln($this->getLang('approve_ready')); 325 ptln('</a>'); 326 } 327 328 if ($helper->client_can_approve($INFO['id'], $approver)) { 329 330 $urlParameters = [ 331 'rev' => $last_approved_rev, 332 'do' => 'diff', 333 'approve' => 'approve' 334 ]; 335 ptln(' | <a href="'.wl($INFO['id'], $urlParameters).'">'); 336 ptln($this->getLang('approve')); 337 ptln('</a>'); 338 } 339 } 340 } 341 342 if ($approver && $this->getConf('banner_long')) { 343 ptln(' | ' . $this->getLang('approver') . ': ' . userlink($approver, true)); 344 } 345 346 ptln('</div>'); 347 } 348 349 /** 350 * @return bool|string|void 351 */ 352 protected function lastRevisionHasntApprovalData($id) { 353 354 try { 355 /** @var \helper_plugin_approve_db $db_helper */ 356 $db_helper = plugin_load('helper', 'approve_db'); 357 $sqlite = $db_helper->getDB(); 358 } catch (Exception $e) { 359 msg($e->getMessage(), -1); 360 return; 361 } 362 363 $res = $sqlite->query('SELECT rev FROM revision 364 WHERE page=? 365 AND current=1 366 AND approved IS NULL 367 AND ready_for_approval IS NULL', $id); 368 369 return $sqlite->res2single($res); 370 } 371 372 /** 373 * 374 * @param Doku_Event $event event object by reference 375 * @return void 376 */ 377 public function handle_pagesave_after(Doku_Event $event) { 378 try { 379 /** @var \helper_plugin_approve_db $db_helper */ 380 $db_helper = plugin_load('helper', 'approve_db'); 381 $sqlite = $db_helper->getDB(); 382 } catch (Exception $e) { 383 msg($e->getMessage(), -1); 384 return; 385 } 386 /** @var helper_plugin_approve $helper */ 387 $helper = plugin_load('helper', 'approve'); 388 389 //no content was changed 390 if (!$event->data['contentChanged']) return; 391 392 $changeType = $event->data['changeType']; 393 if ($changeType == DOKU_CHANGE_TYPE_REVERT) { 394 if ($event->data['oldContent'] == '') { 395 $changeType = DOKU_CHANGE_TYPE_CREATE; 396 } else { 397 $changeType = DOKU_CHANGE_TYPE_EDIT; 398 } 399 } 400 401 $id = $event->data['id']; 402 switch ($changeType) { 403 case DOKU_CHANGE_TYPE_EDIT: 404 case DOKU_CHANGE_TYPE_REVERT: 405 case DOKU_CHANGE_TYPE_MINOR_EDIT: 406 $last_change_date = $event->data['newRevision']; 407 408 //if the current page has approved or ready_for_approval -- keep it 409 $rev = $this->lastRevisionHasntApprovalData($id); 410 if ($rev) { 411 $sqlite->query('UPDATE revision SET rev=? WHERE page=? AND rev=?', 412 $last_change_date, $id, $rev); 413 414 } else { 415 //keep previous record 416 $sqlite->query('UPDATE revision SET current=0 417 WHERE page=? 418 AND current=1', $id); 419 420 $sqlite->storeEntry('revision', [ 421 'page' => $id, 422 'rev' => $last_change_date, 423 'current' => 1 424 ]); 425 } 426 break; 427 case DOKU_CHANGE_TYPE_DELETE: 428 //delete information about availability of a page but keep the history 429 $sqlite->query('DELETE FROM page WHERE page=?', $id); 430 431 //delete revision if no information about approvals 432 $rev = $this->lastRevisionHasntApprovalData($id); 433 if ($rev) { 434 $sqlite->query('DELETE FROM revision WHERE page=? AND rev=?', $id, $rev); 435 } else { 436 $sqlite->query('UPDATE revision SET current=0 WHERE page=? AND current=1', $id); 437 } 438 439 break; 440 case DOKU_CHANGE_TYPE_CREATE: 441 if ($helper->isPageAssigned($sqlite, $id, $newApprover)) { 442 $data = [ 443 'page' => $id, 444 'hidden' => $helper->in_hidden_namespace($sqlite, $id) ? '1' : '0' 445 ]; 446 if (!blank($newApprover)) { 447 $data['approver'] = $newApprover; 448 } 449 $sqlite->storeEntry('page', $data); 450 } 451 452 //store revision 453 $last_change_date = $event->data['newRevision']; 454 $sqlite->storeEntry('revision', [ 455 'page' => $id, 456 'rev' => $last_change_date, 457 'current' => 1 458 ]); 459 break; 460 } 461 } 462} 463