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