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