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