1<?php 2 3use dokuwiki\plugin\approve\meta\ApproveConst; 4 5if(!defined('DOKU_INC')) die(); 6 7class action_plugin_approve_approve extends DokuWiki_Action_Plugin { 8 9 /** @var helper_plugin_sqlite */ 10 protected $sqlite; 11 12 /** 13 * @return helper_plugin_sqlite 14 */ 15 public function sqlite() { 16 if (!$this->sqlite) { 17 $db_helper = plugin_load('helper', 'approve_db'); 18 $this->sqlite = $db_helper->getDB(); 19 } 20 return $this->sqlite; 21 } 22 23 /** 24 * @param Doku_Event_Handler $controller 25 */ 26 public function register(Doku_Event_Handler $controller) { 27 $controller->register_hook('TPL_ACT_RENDER', 'AFTER', $this, 'handle_diff_accept'); 28 $controller->register_hook('HTML_SHOWREV_OUTPUT', 'BEFORE', $this, 'handle_showrev'); 29 $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_approve'); 30 $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_viewer'); 31 $controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'handle_display_banner'); 32 $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'AFTER', $this, 'handle_pagesave_after'); 33 // ensure a page revision is created when summary changes: 34// $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'BEFORE', $this, 'handle_pagesave_before'); 35// $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'AFTER', $this, 'handle_pagesave_after'); 36 } 37 38 /** 39 * @param $id 40 * @return bool 41 */ 42 protected function use_approve_here($id) { 43 $res = $this->sqlite()->query('SELECT page FROM page WHERE page=? AND hidden=0', $id); 44 if ($this->sqlite()->res2single($res)) { 45 return true; 46 } 47 return false; 48 } 49 50 /** 51 * @param $id 52 * @return bool|string 53 */ 54 protected function find_last_approved($id) { 55 $res = $this->sqlite()->query('SELECT rev FROM revision 56 WHERE page=? AND approved IS NOT NULL 57 ORDER BY rev DESC LIMIT 1', $id); 58 return $this->sqlite()->res2single($res); 59 } 60 61 62 /** 63 * @param Doku_Event $event 64 */ 65 public function handle_diff_accept(Doku_Event $event) { 66 global $INFO; 67 68 if (!$this->use_approve_here($INFO['id'])) return; 69 70 if ($event->data == 'diff' && isset($_GET['approve'])) { 71 $href = wl($INFO['id'], ['approve' => 'approve']); 72 ptln('<a href="' . $href . '">'.$this->getLang('approve').'</a>'); 73 } 74 75 if ($this->getConf('ready_for_approval') && $event->data == 'diff' && isset($_GET['ready_for_approval'])) { 76 $href = wl($INFO['id'], ['ready_for_approval' => 'ready_for_approval']); 77 ptln('<a href="' . $href . '">'.$this->getLang('approve').'</a>'); 78 } 79 } 80 81 /** 82 * @param Doku_Event $event 83 */ 84 public function handle_showrev(Doku_Event $event) { 85 global $INFO; 86 87 if (!$this->use_approve_here($INFO['id'])) return; 88 89 $last_approved_rev = $this->find_last_approved($INFO['id']); 90 if ($last_approved_rev == $INFO['rev']) { 91 $event->preventDefault(); 92 } 93 } 94 95// function can_approve() { 96// global $ID; 97// return auth_quickaclcheck($ID) >= AUTH_DELETE; 98// } 99// 100// function can_edit() { 101// global $ID; 102// return auth_quickaclcheck($ID) >= AUTH_EDIT; 103// } 104 105 /** 106 * @param Doku_Event $event 107 */ 108 public function handle_approve(Doku_Event $event) { 109 global $INFO; 110 111 if (!$this->use_approve_here($INFO['id'])) return; 112 113 if ($event->data == 'show' && isset($_GET['approve']) && 114 auth_quickaclcheck($INFO['id']) >= AUTH_DELETE) { 115 116 $res = $this->sqlite()->query('SELECT MAX(version)+1 FROM revision 117 WHERE page=?', $INFO['id']); 118 $next_version = $this->sqlite()->res2single($res); 119 if (!$next_version) { 120 $next_version = 1; 121 } 122 //approved IS NULL prevents from overriding already approved page 123 $this->sqlite()->query('UPDATE revision 124 SET approved=?, version=? 125 WHERE page=? AND current=1 AND approved IS NULL', 126 date('c'), $next_version, $INFO['id']); 127 128 header('Location: ' . wl($INFO['id'])); 129 } elseif ($event->data == 'show' && isset($_GET['ready_for_approval']) && 130 auth_quickaclcheck($INFO['id']) >= AUTH_EDIT) { 131 132 $this->sqlite()->query('UPDATE revision SET ready_for_approval=? 133 WHERE page=? AND current=1 AND ready_for_approval IS NULL', 134 date('c'), $INFO['id']); 135 136 header('Location: ' . wl($INFO['id'])); 137 } 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 if ($event->data != 'show') return; 149 //apply only to current page 150 if (!$INFO['rev']) return; 151 if (auth_quickaclcheck($INFO['id']) >= AUTH_EDIT) return; 152 if (!$this->use_approve_here($INFO['id'])) return; 153 154 $last_approved_rev = $this->find_last_approved($INFO['id']); 155 //no page is approved 156 if (!$last_approved_rev) return; 157 158 $last_change_date = @filemtime(wikiFN($INFO['id'])); 159 //current page is approved 160 if ($last_approved_rev == $last_change_date) return; 161 162 header("Location: " . wl($INFO['id'], ['rev' => $last_approved_rev])); 163 } 164 165// function find_lastest_approved() { 166// global $ID; 167// $m = p_get_metadata($ID); 168// $sum = $m['last_change']['sum']; 169// if ($sum == $this->getConf('sum approved')) 170// return 0; 171// 172// $changelog = new PageChangeLog($ID); 173// 174// $chs = $changelog->getRevisions(0, 10000); 175// foreach ($chs as $rev) { 176// $ch = $changelog->getRevisionInfo($rev); 177// if ($ch['sum'] == $this->getConf('sum approved')) 178// return $rev; 179// } 180// return -1; 181// } 182 183 /** 184 * @param Doku_Event $event 185 */ 186 public function handle_display_banner(Doku_Event $event) { 187 global $INFO; 188 189 if ($event->data != 'show') return; 190 if (!$INFO['exists']) return; 191 if (!$this->use_approve_here($INFO['id'])) return; 192 193// $last_change_date = p_get_metadata($INFO['id'], 'last_change date'); 194 $last_change_date = @filemtime(wikiFN($INFO['id'])); 195 $rev = !$INFO['rev'] ? $last_change_date : $INFO['rev']; 196 197 198 $res = $this->sqlite()->query('SELECT ready_for_approval, approved, version 199 FROM revision 200 WHERE page=? AND rev=?', $INFO['id'], $rev); 201 202 $approve = $this->sqlite()->res_fetch_assoc($res); 203 204 $classes = []; 205 if ($this->getConf('prettyprint')) { 206 $classes[] = 'plugin__approve_noprint'; 207 } 208 209 if ($approve['approved']) { 210 $classes[] = 'plugin__approve_green'; 211 } elseif ($this->getConf('ready_for_approval') && $approve['ready_for_approval']) { 212 $classes[] = 'plugin__approve_ready'; 213 } else { 214 $classes[] = 'plugin__approve_red'; 215 } 216 217 ptln('<div id="plugin__approve" class="' . implode(' ', $classes) . '">'); 218 219 tpl_pageinfo(); 220 ptln(' | '); 221 222 if ($approve['approved']) { 223 ptln('<strong>'.$this->getLang('approved').'</strong> (' 224 . $this->getLang('version') . ': ' . $approve['version'] . ')'); 225 226 //not the newest page 227 if ($rev != $last_change_date) { 228 $res = $this->sqlite()->query('SELECT rev, current FROM revision 229 WHERE page=? AND approved IS NOT NULL 230 ORDER BY rev DESC LIMIT 1', $INFO['id']); 231 232 $lastest_approve = $this->sqlite()->res_fetch_assoc($res); 233 234 //we can see drafts 235 if (auth_quickaclcheck($INFO['id']) >= AUTH_EDIT) { 236 ptln('<a href="' . wl($INFO['id']) . '">'); 237 ptln($this->getLang($lastest_approve['current'] ? 'newest_approved' : 'newest_draft')); 238 ptln('</a>'); 239 } else { 240 $urlParameters = []; 241 if (!$lastest_approve['current']) { 242 $urlParameters['rev'] = $lastest_approve['rev']; 243 } 244 ptln('<a href="' . wl($INFO['id'], $urlParameters) . '">'); 245 ptln($this->getLang('newest_approved')); 246 ptln('</a>'); 247 } 248 } 249 250 } else { 251 ptln('<span>'.$this->getLang('draft').'</span>'); 252 253 if ($this->getConf('ready_for_approval') && $approve['ready_for_approval']) { 254 ptln('<span>| '.$this->getLang('marked_approve_ready').'</span>'); 255 } 256 257 258 $res = $this->sqlite()->query('SELECT rev, current FROM revision 259 WHERE page=? AND approved IS NOT NULL 260 ORDER BY rev DESC LIMIT 1', $INFO['id']); 261 262 $lastest_approve = $this->sqlite()->res_fetch_assoc($res); 263 264 265 //not exists approve for current page 266 if (!$lastest_approve) { 267 //not the newest page 268 if ($rev != $last_change_date) { 269 ptln('<a href="'.wl($INFO['id']).'">'); 270 ptln($this->getLang('newest_draft')); 271 ptln('</a>'); 272 } 273 } else { 274 $urlParameters = []; 275 if (!$lastest_approve['current']) { 276 $urlParameters['rev'] = $lastest_approve['rev']; 277 } 278 ptln('<a href="' . wl($INFO['id'], $urlParameters) . '">'); 279 ptln($this->getLang('newest_approved')); 280 ptln('</a>'); 281 } 282 283 //we are in current page 284 if ($rev == $last_change_date) { 285 286 $last_approved_rev = 0; 287 if (isset($lastest_approve['rev'])) { 288 $last_approved_rev = $lastest_approve['rev']; 289 } 290 291 if ($this->getConf('ready_for_approval') && 292 auth_quickaclcheck($INFO['id']) >= AUTH_EDIT && 293 !$approve['ready_for_approval']) { 294 295 $urlParameters = [ 296 'rev' => $last_approved_rev, 297 'do' => 'diff', 298 'ready_for_approval' => 'ready_for_approval' 299 ]; 300 ptln(' | <a href="'.wl($INFO['id'], $urlParameters).'">'); 301 ptln($this->getLang('approve_ready')); 302 ptln('</a>'); 303 } 304 305 if (auth_quickaclcheck($INFO['id']) >= AUTH_DELETE) { 306 307 $urlParameters = [ 308 'rev' => $last_approved_rev, 309 'do' => 'diff', 310 'approve' => 'approve' 311 ]; 312 ptln(' | <a href="'.wl($INFO['id'], $urlParameters).'">'); 313 ptln($this->getLang('approve')); 314 ptln('</a>'); 315 } 316 317 } 318 } 319 ptln('</div>'); 320 } 321 322 /** 323 * @return bool|string 324 */ 325 protected function prev_rev($id) { 326 $res = $this->sqlite()->query('SELECT rev FROM revision 327 WHERE page=? 328 AND current=1 329 AND approved IS NULL 330 AND ready_for_approval IS NULL', $id); 331 332 return $this->sqlite()->res2single($res); 333 } 334 335 /** 336 * 337 * @param Doku_Event $event event object by reference 338 * @return void 339 */ 340 public function handle_pagesave_after(Doku_Event $event) { 341 //no content was mde 342 if (!$event->data['contentChanged']) return; 343 344 $changeType = $event->data['changeType']; 345 if ($changeType == DOKU_CHANGE_TYPE_REVERT) { 346 if ($event->data['oldContent'] == '') { 347 $changeType = DOKU_CHANGE_TYPE_CREATE; 348 } else { 349 $changeType = DOKU_CHANGE_TYPE_EDIT; 350 } 351 } 352 353 $id = $event->data['id']; 354 //TODO: if created, if deleted 355 switch ($changeType) { 356 case DOKU_CHANGE_TYPE_EDIT: 357 case DOKU_CHANGE_TYPE_REVERT: 358 case DOKU_CHANGE_TYPE_MINOR_EDIT: 359 $last_change_date = $event->data['newRevision']; 360 361 //if the current page has approved or ready_for_approval -- keep it 362 $prev_rev = $this->prev_rev($id); 363 if ($prev_rev) { 364 $this->sqlite()->query('UPDATE revision SET rev=? WHERE page=? AND rev=?', 365 $last_change_date, $id, $prev_rev); 366 367 } else { 368 //keep previous record 369 $this->sqlite()->query('UPDATE revision SET current=0 370 WHERE page=? 371 AND current=1', $id); 372 373 $this->sqlite()->storeEntry('revision', [ 374 'page' => $id, 375 'rev' => $last_change_date, 376 'current' => 1 377 ]); 378 } 379 break; 380 case DOKU_CHANGE_TYPE_DELETE: 381 //delete information about availability of a page but keep the history 382 $this->sqlite()->query('DELETE FROM page WHERE page=?', $id); 383 384 //delete revision if no information about approvals 385 $prev_rev = $this->prev_rev($id); 386 if ($prev_rev) { 387 $this->sqlite()->query('DELETE FROM revision WHERE page=? AND rev=?', 388 $id, $prev_rev); 389 } else { 390 $this->sqlite()->query('UPDATE revision SET current=0 WHERE page=? AND rev=?', 391 $id, $prev_rev); 392 } 393 394 break; 395 case DOKU_CHANGE_TYPE_CREATE: 396 $last_change_date = $event->data['newRevision']; 397 //TODO should be checked against no_approve_rev 398 $this->sqlite()->storeEntry('page', [ 399 'page' => $id 400 ]); 401 402 $this->sqlite()->storeEntry('revision', [ 403 'page' => $id, 404 'rev' => $last_change_date, 405 'current' => 1 406 ]); 407 break; 408 } 409 } 410 411 /** 412 * Check if the page has to be changed 413 * 414 * @param Doku_Event $event event object by reference 415 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 416 * handler was registered] 417 * @return void 418 */ 419// public function handle_pagesave_before(Doku_Event $event, $param) { 420// global $REV; 421// $id = $event->data['id']; 422// if (!$this->hlp->use_approve_here($id)) return; 423// 424// //save page if summary is provided 425// if($event->data['summary'] == $this->getConf('sum approved') || 426// $event->data['summary'] == $this->getConf('sum ready for approval')) { 427// $event->data['contentChanged'] = true; 428// } 429// } 430 431 /** 432 * @param Doku_Event $event 433 * @param $param 434 */ 435// public function handle_pagesave_after(Doku_Event $event, $param) { 436// global $REV; 437// $id = $event->data['id']; 438// if (!$this->hlp->use_approve_here($id)) return; 439// 440// //save page if summary is provided 441// if($event->data['summary'] == $this->getConf('sum approved')) { 442// 443// $versions = p_get_metadata($id, ApproveConst::METADATA_VERSIONS_KEY); 444// //calculate versions 445// if (!$versions) { 446// $this->render_metadata_for_approved_page($id, $event->data['newRevision']); 447// } else { 448// $curver = $versions[0] + 1; 449// $versions[0] = $curver; 450// $versions[$event->data['newRevision']] = $curver; 451// p_set_metadata($id, array(ApproveConst::METADATA_VERSIONS_KEY => $versions)); 452// } 453// } 454// } 455 456 457 /** 458 * Calculate current version 459 * 460 * @param $id 461 * @return array 462 */ 463// protected function render_metadata_for_approved_page($id, $currev=false) { 464// if (!$currev) $currev = @filemtime(wikiFN($id)); 465// 466// $version = $this->approved($id); 467// //version for current page 468// $curver = $version + 1; 469// $versions = array(0 => $curver, $currev => $curver); 470// 471// $changelog = new PageChangeLog($id); 472// $first = 0; 473// $num = 100; 474// while (count($revs = $changelog->getRevisions($first, $num)) > 0) { 475// foreach ($revs as $rev) { 476// $revInfo = $changelog->getRevisionInfo($rev); 477// if ($revInfo['sum'] == $this->getConf('sum approved')) { 478// $versions[$rev] = $version; 479// $version -= 1; 480// } 481// } 482// $first += $num; 483// } 484// 485// p_set_metadata($id, array(ApproveConst::METADATA_VERSIONS_KEY => $versions)); 486// 487// return $versions; 488// } 489 490 /** 491 * Get the number of approved pages 492 * @param $id 493 * @return int 494 */ 495// protected function approved($id) { 496// $count = 0; 497// 498// $changelog = new PageChangeLog($id); 499// $first = 0; 500// $num = 100; 501// while (count($revs = $changelog->getRevisions($first, $num)) > 0) { 502// foreach ($revs as $rev) { 503// $revInfo = $changelog->getRevisionInfo($rev); 504// if ($revInfo['sum'] == $this->getConf('sum approved')) { 505// $count += 1; 506// } 507// } 508// $first += $num; 509// } 510// 511// return $count; 512// } 513} 514