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