1<?php 2/** 3 * DokuWiki Plugin publish (Helper Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Jarrod Lowe <dokuwiki@rrod.net> 7 * @author Andreas Gohr <gohr@cosmocode.de> 8 */ 9 10// must be run within Dokuwiki 11if (!defined('DOKU_INC')) die(); 12 13class helper_plugin_publish extends DokuWiki_Plugin { 14 15 private $sortedApprovedRevisions = null; 16 17 /** 18 * checks if an id is within one of the namespaces in $namespace_list 19 * 20 * @param string $namespace_list 21 * @param string $id 22 * 23 * @return bool 24 */ 25 function in_namespace($namespace_list, $id) { 26 // PHP apparantly does not have closures - 27 // so we will parse $valid ourselves. Wasteful. 28 $namespace_list = preg_split('/\s+/', $namespace_list); 29 //if(count($valid) == 0) { return true; }//whole wiki matches 30 if((count($namespace_list)==1) and ($namespace_list[0]=="")) { return true; }//whole wiki matches 31 $id = trim($id, ':'); 32 $id = explode(':', $id); 33 34 // Check against all possible namespaces 35 foreach($namespace_list as $namespace) { 36 $namespace = explode(':', $namespace); 37 $current_ns_depth = 0; 38 $total_ns_depth = count($namespace); 39 $matching = true; 40 41 // Check each element, untill all elements of $v satisfied 42 while($current_ns_depth < $total_ns_depth) { 43 if($namespace[$current_ns_depth] != $id[$current_ns_depth]) { 44 // not a match 45 $matching = false; 46 break; 47 } 48 $current_ns_depth += 1; 49 } 50 if($matching) { return true; } // a match 51 } 52 return false; 53 } 54 55 /** 56 * check if given $dir contains a valid namespace or is contained in a valid namespace 57 * 58 * @param $valid_namespaces_list 59 * @param $dir 60 * 61 * @return bool 62 */ 63 function is_dir_valid($valid_namespaces_list, $dir) { 64 $valid_namespaces_list = preg_split('/\s+/', $valid_namespaces_list); 65 //if(count($valid) == 0) { return true; }//whole wiki matches 66 if((count($valid_namespaces_list)==1) && ($valid_namespaces_list[0]=="")) { return true; }//whole wiki matches 67 $dir = trim($dir, ':'); 68 $dir = explode(':', $dir); 69 70 // Check against all possible namespaces 71 foreach($valid_namespaces_list as $valid_namespace) { 72 $valid_namespace = explode(':', $valid_namespace); 73 $current_depth = 0; 74 $dir_depth = count($dir); //this is what is different from above! 75 $matching = true; 76 77 // Check each element, untill all elements of $v satisfied 78 while($current_depth < $dir_depth) { 79 if (empty($valid_namespace[$current_depth])) { 80 break; 81 } 82 if($valid_namespace[$current_depth] != $dir[$current_depth]) { 83 // not a match 84 $matching = false; 85 break; 86 } 87 $current_depth += 1; 88 } 89 if($matching) { return true; } // a match 90 } 91 return false; 92 } 93 94 function canApprove() { 95 global $INFO; 96 global $ID; 97 98 if (!$this->in_namespace($this->getConf('apr_namespaces'), $ID)) { 99 return false; 100 } 101 102 return ($INFO['perm'] >= AUTH_DELETE); 103 } 104 105 function getRevision($id = null) { 106 global $REV; 107 if (isset($REV) && !empty($REV)) { 108 return $REV; 109 } 110 $meta = $this->getMeta($id); 111 if (isset($meta['last_change']['date'])) { 112 return $meta['last_change']['date']; 113 } 114 return $meta['date']['modified']; 115 } 116 117 function getApprovals($id = null) { 118 $meta = $this->getMeta($id); 119 if (!isset($meta['approval'])) { 120 return array(); 121 } 122 $approvals = $meta['approval']; 123 if (!is_array($approvals)) { 124 return array(); 125 } 126 return $approvals; 127 } 128 129 function getMeta($id = null) { 130 global $ID; 131 global $INFO; 132 133 if ($id === null) $id = $ID; 134 135 if($ID === $id && $INFO['meta']) { 136 $meta = $INFO['meta']; 137 } else { 138 $meta = p_get_metadata($id); 139 } 140 141 $this->checkApprovalFormat($meta, $id); 142 143 return $meta; 144 } 145 146 function checkApprovalFormat($meta, $id) { 147 if (isset($meta['approval_version']) && $meta['approval_version'] >= 2) { 148 return; 149 } 150 151 if (!$this->hasApprovals($meta)) { 152 return; 153 } 154 155 $approvals = $meta['approval']; 156 foreach (array_keys($approvals) as $approvedId) { 157 $keys = array_keys($approvals[$approvedId]); 158 159 if (is_array($approvals[$approvedId][$keys[0]])) { 160 continue; // current format 161 } 162 163 $newEntry = $approvals[$approvedId]; 164 if (count($newEntry) !== 3) { 165 //continue; // some messed up format... 166 } 167 $newEntry[] = intval($approvedId); // revision is the time of page edit 168 169 $approvals[$approvedId] = array(); 170 $approvals[$approvedId][$newEntry[0]] = $newEntry; 171 } 172 p_set_metadata($id, array('approval' => $approvals), true, true); 173 p_set_metadata($id, array('approval_version' => 2), true, true); 174 } 175 176 function hasApprovals($meta) { 177 return isset($meta['approval']) && !empty($meta['approval']); 178 } 179 180 function getApprovalsOnRevision($revision) { 181 $approvals = $this->getApprovals(); 182 183 if (isset($approvals[$revision])) { 184 return $approvals[$revision]; 185 } 186 return array(); 187 } 188 189 function getSortedApprovedRevisions($id = null) { 190 if ($id === null) { 191 global $ID; 192 $id = $ID; 193 } 194 195 static $sortedApprovedRevisions = array(); 196 if (!isset($sortedApprovedRevisions[$id])) { 197 $approvals = $this->getApprovals($id); 198 krsort($approvals); 199 $sortedApprovedRevisions[$id] = $approvals; 200 } 201 202 return $sortedApprovedRevisions[$id]; 203 } 204 205 function isRevisionApproved($revision, $id = null) { 206 $approvals = $this->getApprovals($id); 207 if (!isset($approvals[$revision])) { 208 return false; 209 } 210 return (count($approvals[$revision]) >= $this->getConf('number_of_approved')); 211 } 212 213 function isCurrentRevisionApproved($id = null) { 214 return $this->isRevisionApproved($this->getRevision($id), $id); 215 } 216 217 function getLatestApprovedRevision($id = null) { 218 $approvals = $this->getSortedApprovedRevisions($id); 219 foreach ($approvals as $revision => $ignored) { 220 if ($this->isRevisionApproved($revision, $id)) { 221 return $revision; 222 } 223 } 224 return 0; 225 } 226 227 function getLastestRevision() { 228 global $INFO; 229 return $INFO['meta']['date']['modified']; 230 } 231 232 function getApprovalDate() { 233 if (!$this->isCurrentRevisionApproved()) { 234 return -1; 235 } 236 237 $approvals = $this->getApprovalsOnRevision($this->getRevision()); 238 uasort($approvals, array(&$this, 'cmpApprovals')); 239 $keys = array_keys($approvals); 240 return $approvals[$keys[$this->getConf('number_of_approved') -1]][3]; 241 242 } 243 244 function cmpApprovals($left, $right) { 245 if ($left[3] == $right[3]) { 246 return 0; 247 } 248 return ($left[3] < $right[3]) ? -1 : 1; 249 } 250 251 function getApprovers() { 252 $approvers = $this->getApprovalsOnRevision($this->getRevision()); 253 if (count($approvers) === 0) { 254 return; 255 } 256 257 $result = array(); 258 foreach ($approvers as $approver) { 259 $result[] = editorinfo($this->getApproverName($approver)); 260 } 261 return $result; 262 } 263 264 function getApproverName($approver) { 265 if ($approver[1]) { 266 return $approver[1]; 267 } 268 if ($approver[2]) { 269 return $approver[2]; 270 } 271 return $approver[0]; 272 } 273 274 function getPreviousApprovedRevision() { 275 $currentRevision = $this->getRevision(); 276 $approvals = $this->getSortedApprovedRevisions(); 277 foreach ($approvals as $revision => $ignored) { 278 if ($revision >= $currentRevision) { 279 continue; 280 } 281 if ($this->isRevisionApproved($revision)) { 282 return $revision; 283 } 284 } 285 return 0; 286 } 287 288 function isHidden($id = null) { 289 if (!$this->getConf('hide drafts')) { 290 return false; 291 } 292 293 // needs to check if the actual namespace belongs to the apr_namespaces 294 if ($id == null) { 295 global $ID; 296 $id = $ID; 297 } 298 if (!$this->isActive($id)) { 299 return false; 300 } 301 302 if ($this->getLatestApprovedRevision($id)) { 303 return false; 304 } 305 return true; 306 } 307 308 function isHiddenForUser($id = null) { 309 if (!$this->isHidden($id)) { 310 return false; 311 } 312 313 if ($id == null) { 314 global $ID; 315 $id = $ID; 316 } 317 318 $allowedGroups = array_filter(explode(' ', trim($this->getConf('author groups')))); 319 if (empty($allowedGroups)) { 320 return auth_quickaclcheck($id) < AUTH_EDIT; 321 } 322 323 if (!$_SERVER['REMOTE_USER']) { 324 return true; 325 } 326 327 global $USERINFO; 328 foreach ($allowedGroups as $allowedGroup) { 329 $allowedGroup = trim($allowedGroup); 330 if (in_array($allowedGroup, $USERINFO['grps'])) { 331 return false; 332 } 333 } 334 return true; 335 } 336 337 function isActive($id = null) { 338 if ($id == null) { 339 global $ID; 340 $id = $ID; 341 } 342 if (!$this->in_namespace($this->getConf('apr_namespaces'), $id)) { 343 return false; 344 } 345 346 $no_apr_namespaces = $this->getConf('no_apr_namespaces'); 347 if (!empty($no_apr_namespaces)) { 348 if ($this->in_namespace($no_apr_namespaces, $id)) { 349 return false; 350 } 351 } 352 return true; 353 } 354 355 /** 356 * Create absolute diff-link between the two given revisions 357 * 358 * @param string $id 359 * @param int $rev1 360 * @param int $rev2 361 * @return string Diff-Link or empty string if $rev1 == $rev2 362 */ 363 public function getDifflink($id, $rev1, $rev2) { 364 if($rev1 == $rev2) { 365 return ''; 366 } 367 $params = 'do=diff,rev2[0]=' . $rev1 . ',rev2[1]=' . $rev2 . ',difftype=sidebyside'; 368 $difflink = wl($id, $params,true,'&'); 369 return $difflink; 370 } 371 372 function getPagesFromNamespace($namespace) { 373 global $conf; 374 $dir = $conf['datadir'] . '/' . str_replace(':', '/', $namespace); 375 $pages = array(); 376 search($pages, $dir, array($this,'_search_helper'), array($namespace, $this->getConf('apr_namespaces'), 377 $this->getConf('no_apr_namespaces'))); 378 return $pages; 379 } 380 381 /** 382 * search callback function 383 * 384 * filter out pages which can't be approved by the current user 385 * then check if they need approving 386 */ 387 function _search_helper(&$data, $base, $file, $type, $lvl, $opts) { 388 $ns = $opts[0]; 389 $valid_ns = $opts[1]; 390 $invalid_ns = $opts[2]; 391 392 if ($type == 'd') { 393 return $this->is_dir_valid($valid_ns, $ns . ':' . str_replace('/', ':', $file)); 394 } 395 396 if (!preg_match('#\.txt$#', $file)) { 397 return false; 398 } 399 400 $id = pathID($ns . $file); 401 if (!empty($valid_ns) && !$this->in_namespace($valid_ns, $id)) { 402 return false; 403 } 404 405 if (!empty($invalid_ns) && $this->in_namespace($invalid_ns, $id)) { 406 return false; 407 } 408 409 if (auth_quickaclcheck($id) < AUTH_DELETE) { 410 return false; 411 } 412 413 $meta = $this->getMeta($id); 414 if ($this->isCurrentRevisionApproved($id)) { 415 416 // Already approved 417 return false; 418 } 419 420 $data[] = array($id, $meta['approval'], $meta['last_change']['date']); 421 return false; 422 } 423 424 public function removeSubnamespacePages ($pages, $namespace) { 425 $cleanpages = array(); 426 foreach ($pages as $page) { 427 if (getNS($page[0]) == $namespace) { 428 $cleanpages[] = $page; 429 } 430 } 431 return $cleanpages; 432 } 433 434} 435