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 150 if ($event->data != 'show') return; 151 if (!$INFO['exists']) return; 152 if (!$this->helper()->use_approve_here($INFO['id'], $maintainer)) return; 153 154// $last_change_date = p_get_metadata($INFO['id'], 'last_change date'); 155 $last_change_date = @filemtime(wikiFN($INFO['id'])); 156 $rev = !$INFO['rev'] ? $last_change_date : $INFO['rev']; 157 158 159 $res = $this->sqlite()->query('SELECT ready_for_approval, ready_for_approval_by, 160 approved, approved_by, version 161 FROM revision 162 WHERE page=? AND rev=?', $INFO['id'], $rev); 163 164 $approve = $this->sqlite()->res_fetch_assoc($res); 165 166 $classes = []; 167 if ($this->getConf('prettyprint')) { 168 $classes[] = 'plugin__approve_noprint'; 169 } 170 171 if ($approve['approved']) { 172 $classes[] = 'plugin__approve_green'; 173 } elseif ($this->getConf('ready_for_approval') && $approve['ready_for_approval']) { 174 $classes[] = 'plugin__approve_ready'; 175 } else { 176 $classes[] = 'plugin__approve_red'; 177 } 178 179 ptln('<div id="plugin__approve" class="' . implode(' ', $classes) . '">'); 180 181// tpl_pageinfo(); 182// ptln(' | '); 183 184 if ($approve['approved']) { 185 ptln('<strong>'.$this->getLang('approved').'</strong>'); 186 ptln(' ' . dformat(strtotime($approve['approved']))); 187 ptln(' ' . $this->getLang('by') . ' ' . userlink($approve['approved_by'], true)); 188 ptln(' (' . $this->getLang('version') . ': ' . $approve['version'] . ')'); 189 190 //not the newest page 191 if ($rev != $last_change_date) { 192 $res = $this->sqlite()->query('SELECT rev, current FROM revision 193 WHERE page=? AND approved IS NOT NULL 194 ORDER BY rev DESC LIMIT 1', $INFO['id']); 195 196 $last_approve = $this->sqlite()->res_fetch_assoc($res); 197 198 //we can see drafts 199 if (auth_quickaclcheck($INFO['id']) >= AUTH_EDIT) { 200 ptln('<a href="' . wl($INFO['id']) . '">'); 201 ptln($this->getLang($last_approve['current'] ? 'newest_approved' : 'newest_draft')); 202 ptln('</a>'); 203 //we cannot see link to draft but there is some newer approved version 204 } elseif ($last_approve['rev'] != $rev) { 205 $urlParameters = []; 206 if (!$last_approve['current']) { 207 $urlParameters['rev'] = $last_approve['rev']; 208 } 209 ptln('<a href="' . wl($INFO['id'], $urlParameters) . '">'); 210 ptln($this->getLang('newest_approved')); 211 ptln('</a>'); 212 } 213 } 214 215 } else { 216 if ($this->getConf('ready_for_approval') && $approve['ready_for_approval']) { 217 ptln('<strong>'.$this->getLang('marked_approve_ready').'</strong>'); 218 ptln(' ' . dformat(strtotime($approve['ready_for_approval']))); 219 ptln(' ' . $this->getLang('by') . ' ' . userlink($approve['ready_for_approval_by'], true)); 220 } else { 221 ptln('<strong>'.$this->getLang('draft').'</strong>'); 222 } 223 224 225 $res = $this->sqlite()->query('SELECT rev, current FROM revision 226 WHERE page=? AND approved IS NOT NULL 227 ORDER BY rev DESC LIMIT 1', $INFO['id']); 228 229 $last_approve = $this->sqlite()->res_fetch_assoc($res); 230 231 232 //not exists approve for current page 233 if (!$last_approve) { 234 //not the newest page 235 if ($rev != $last_change_date) { 236 ptln('<a href="'.wl($INFO['id']).'">'); 237 ptln($this->getLang('newest_draft')); 238 ptln('</a>'); 239 } 240 } else { 241 $urlParameters = []; 242 if (!$last_approve['current']) { 243 $urlParameters['rev'] = $last_approve['rev']; 244 } 245 ptln('<a href="' . wl($INFO['id'], $urlParameters) . '">'); 246 ptln($this->getLang('newest_approved')); 247 ptln('</a>'); 248 } 249 250 //we are in current page 251 if ($rev == $last_change_date) { 252 253 //compare with the last approved page or 0 if there is no approved versions 254 $last_approved_rev = 0; 255 if (isset($last_approve['rev'])) { 256 $last_approved_rev = $last_approve['rev']; 257 } 258 259 if ($this->getConf('ready_for_approval') && 260 auth_quickaclcheck($INFO['id']) >= AUTH_EDIT && 261 !$approve['ready_for_approval']) { 262 263 $urlParameters = [ 264 'rev' => $last_approved_rev, 265 'do' => 'diff', 266 'ready_for_approval' => 'ready_for_approval' 267 ]; 268 ptln(' | <a href="'.wl($INFO['id'], $urlParameters).'">'); 269 ptln($this->getLang('approve_ready')); 270 ptln('</a>'); 271 } 272 273 if (auth_quickaclcheck($INFO['id']) >= AUTH_DELETE) { 274 275 $urlParameters = [ 276 'rev' => $last_approved_rev, 277 'do' => 'diff', 278 'approve' => 'approve' 279 ]; 280 ptln(' | <a href="'.wl($INFO['id'], $urlParameters).'">'); 281 ptln($this->getLang('approve')); 282 ptln('</a>'); 283 } 284 } 285 } 286 287 if ($maintainer) { 288 ptln(' | ' . $this->getLang('maintainer') . ': ' . userlink($maintainer, true)); 289 } 290 291 ptln('</div>'); 292 } 293 294 /** 295 * @return bool|string 296 */ 297 protected function prev_rev($id) { 298 $res = $this->sqlite()->query('SELECT rev FROM revision 299 WHERE page=? 300 AND current=1 301 AND approved IS NULL 302 AND ready_for_approval IS NULL', $id); 303 304 return $this->sqlite()->res2single($res); 305 } 306 307 /** 308 * 309 * @param Doku_Event $event event object by reference 310 * @return void 311 */ 312 public function handle_pagesave_after(Doku_Event $event) { 313 //no content was changed 314 if (!$event->data['contentChanged']) return; 315 316 $changeType = $event->data['changeType']; 317 if ($changeType == DOKU_CHANGE_TYPE_REVERT) { 318 if ($event->data['oldContent'] == '') { 319 $changeType = DOKU_CHANGE_TYPE_CREATE; 320 } else { 321 $changeType = DOKU_CHANGE_TYPE_EDIT; 322 } 323 } 324 325 $id = $event->data['id']; 326 switch ($changeType) { 327 case DOKU_CHANGE_TYPE_EDIT: 328 case DOKU_CHANGE_TYPE_REVERT: 329 case DOKU_CHANGE_TYPE_MINOR_EDIT: 330 $last_change_date = $event->data['newRevision']; 331 332 //if the current page has approved or ready_for_approval -- keep it 333 $prev_rev = $this->prev_rev($id); 334 if ($prev_rev) { 335 $this->sqlite()->query('UPDATE revision SET rev=? WHERE page=? AND rev=?', 336 $last_change_date, $id, $prev_rev); 337 338 } else { 339 //keep previous record 340 $this->sqlite()->query('UPDATE revision SET current=0 341 WHERE page=? 342 AND current=1', $id); 343 344 $this->sqlite()->storeEntry('revision', [ 345 'page' => $id, 346 'rev' => $last_change_date, 347 'current' => 1 348 ]); 349 } 350 break; 351 case DOKU_CHANGE_TYPE_DELETE: 352 //delete information about availability of a page but keep the history 353 $this->sqlite()->query('DELETE FROM page WHERE page=?', $id); 354 355 //delete revision if no information about approvals 356 $prev_rev = $this->prev_rev($id); 357 if ($prev_rev) { 358 $this->sqlite()->query('DELETE FROM revision WHERE page=? AND rev=?', 359 $id, $prev_rev); 360 } else { 361 $this->sqlite()->query('UPDATE revision SET current=0 WHERE page=? AND rev=?', 362 $id, $prev_rev); 363 } 364 365 break; 366 case DOKU_CHANGE_TYPE_CREATE: 367 $last_change_date = $event->data['newRevision']; 368 $hidden = $this->helper()->in_hidden_namespace($id); 369 370 $this->sqlite()->storeEntry('page', [ 371 'page' => $id, 372 'hidden' => $hidden 373 ]); 374 375 $this->sqlite()->storeEntry('revision', [ 376 'page' => $id, 377 'rev' => $last_change_date, 378 'current' => 1 379 ]); 380 break; 381 } 382 } 383} 384