1*7d559c7fSBen Coburn<?php 2*7d559c7fSBen Coburn/** 3*7d559c7fSBen Coburn * Changelog handling functions 4*7d559c7fSBen Coburn * 5*7d559c7fSBen Coburn * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6*7d559c7fSBen Coburn * @author Andreas Gohr <andi@splitbrain.org> 7*7d559c7fSBen Coburn */ 8*7d559c7fSBen Coburn 9*7d559c7fSBen Coburn/** 10*7d559c7fSBen Coburn * parses a changelog line into it's components 11*7d559c7fSBen Coburn * 12*7d559c7fSBen Coburn * @author Ben Coburn <btcoburn@silicodon.net> 13*7d559c7fSBen Coburn */ 14*7d559c7fSBen Coburnfunction parseChangelogLine($line) { 15*7d559c7fSBen Coburn $tmp = explode("\t", $line); 16*7d559c7fSBen Coburn if ($tmp!==false && count($tmp)>1) { 17*7d559c7fSBen Coburn $info = array(); 18*7d559c7fSBen Coburn $info['date'] = $tmp[0]; // unix timestamp 19*7d559c7fSBen Coburn $info['ip'] = $tmp[1]; // IPv4 address (127.0.0.1) 20*7d559c7fSBen Coburn $info['type'] = $tmp[2]; // log line type 21*7d559c7fSBen Coburn $info['id'] = $tmp[3]; // page id 22*7d559c7fSBen Coburn $info['user'] = $tmp[4]; // user name 23*7d559c7fSBen Coburn $info['sum'] = $tmp[5]; // edit summary (or action reason) 24*7d559c7fSBen Coburn $info['extra'] = rtrim($tmp[6], "\n"); // extra data (varies by line type) 25*7d559c7fSBen Coburn return $info; 26*7d559c7fSBen Coburn } else { return false; } 27*7d559c7fSBen Coburn} 28*7d559c7fSBen Coburn 29*7d559c7fSBen Coburn/** 30*7d559c7fSBen Coburn * Add's an entry to the changelog and saves the metadata for the page 31*7d559c7fSBen Coburn * 32*7d559c7fSBen Coburn * @author Andreas Gohr <andi@splitbrain.org> 33*7d559c7fSBen Coburn * @author Esther Brunner <wikidesign@gmail.com> 34*7d559c7fSBen Coburn * @author Ben Coburn <btcoburn@silicodon.net> 35*7d559c7fSBen Coburn */ 36*7d559c7fSBen Coburnfunction addLogEntry($date, $id, $type='E', $summary='', $extra=''){ 37*7d559c7fSBen Coburn global $conf, $INFO; 38*7d559c7fSBen Coburn 39*7d559c7fSBen Coburn $id = cleanid($id); 40*7d559c7fSBen Coburn $file = wikiFN($id); 41*7d559c7fSBen Coburn $created = @filectime($file); 42*7d559c7fSBen Coburn $minor = ($type==='e'); 43*7d559c7fSBen Coburn $wasRemoved = ($type==='D'); 44*7d559c7fSBen Coburn 45*7d559c7fSBen Coburn if(!$date) $date = time(); //use current time if none supplied 46*7d559c7fSBen Coburn $remote = $_SERVER['REMOTE_ADDR']; 47*7d559c7fSBen Coburn $user = $_SERVER['REMOTE_USER']; 48*7d559c7fSBen Coburn 49*7d559c7fSBen Coburn $strip = array("\t", "\n"); 50*7d559c7fSBen Coburn $logline = array( 51*7d559c7fSBen Coburn 'date' => $date, 52*7d559c7fSBen Coburn 'ip' => $remote, 53*7d559c7fSBen Coburn 'type' => str_replace($strip, '', $type), 54*7d559c7fSBen Coburn 'id' => $id, 55*7d559c7fSBen Coburn 'user' => $user, 56*7d559c7fSBen Coburn 'sum' => str_replace($strip, '', $summary), 57*7d559c7fSBen Coburn 'extra' => str_replace($strip, '', $extra) 58*7d559c7fSBen Coburn ); 59*7d559c7fSBen Coburn 60*7d559c7fSBen Coburn // update metadata 61*7d559c7fSBen Coburn if (!$wasRemoved) { 62*7d559c7fSBen Coburn $meta = array(); 63*7d559c7fSBen Coburn if (!$INFO['exists']){ // newly created 64*7d559c7fSBen Coburn $meta['date']['created'] = $created; 65*7d559c7fSBen Coburn if ($user) $meta['creator'] = $INFO['userinfo']['name']; 66*7d559c7fSBen Coburn } elseif (!$minor) { // non-minor modification 67*7d559c7fSBen Coburn $meta['date']['modified'] = $date; 68*7d559c7fSBen Coburn if ($user) $meta['contributor'][$user] = $INFO['userinfo']['name']; 69*7d559c7fSBen Coburn } 70*7d559c7fSBen Coburn $meta['last_change'] = $logline; 71*7d559c7fSBen Coburn p_set_metadata($id, $meta, true); 72*7d559c7fSBen Coburn } 73*7d559c7fSBen Coburn 74*7d559c7fSBen Coburn // add changelog lines 75*7d559c7fSBen Coburn $logline = implode("\t", $logline)."\n"; 76*7d559c7fSBen Coburn io_saveFile(metaFN($id,'.changes'),$logline,true); //page changelog 77*7d559c7fSBen Coburn io_saveFile($conf['changelog'],$logline,true); //global changelog cache 78*7d559c7fSBen Coburn} 79*7d559c7fSBen Coburn 80*7d559c7fSBen Coburn/** 81*7d559c7fSBen Coburn * returns an array of recently changed files using the 82*7d559c7fSBen Coburn * changelog 83*7d559c7fSBen Coburn * 84*7d559c7fSBen Coburn * The following constants can be used to control which changes are 85*7d559c7fSBen Coburn * included. Add them together as needed. 86*7d559c7fSBen Coburn * 87*7d559c7fSBen Coburn * RECENTS_SKIP_DELETED - don't include deleted pages 88*7d559c7fSBen Coburn * RECENTS_SKIP_MINORS - don't include minor changes 89*7d559c7fSBen Coburn * RECENTS_SKIP_SUBSPACES - don't include subspaces 90*7d559c7fSBen Coburn * 91*7d559c7fSBen Coburn * @param int $first number of first entry returned (for paginating 92*7d559c7fSBen Coburn * @param int $num return $num entries 93*7d559c7fSBen Coburn * @param string $ns restrict to given namespace 94*7d559c7fSBen Coburn * @param bool $flags see above 95*7d559c7fSBen Coburn * 96*7d559c7fSBen Coburn * @author Ben Coburn <btcoburn@silicodon.net> 97*7d559c7fSBen Coburn */ 98*7d559c7fSBen Coburnfunction getRecents($first,$num,$ns='',$flags=0){ 99*7d559c7fSBen Coburn global $conf; 100*7d559c7fSBen Coburn $recent = array(); 101*7d559c7fSBen Coburn $count = 0; 102*7d559c7fSBen Coburn 103*7d559c7fSBen Coburn if(!$num) 104*7d559c7fSBen Coburn return $recent; 105*7d559c7fSBen Coburn 106*7d559c7fSBen Coburn // read all recent changes. (kept short) 107*7d559c7fSBen Coburn $lines = file($conf['changelog']); 108*7d559c7fSBen Coburn 109*7d559c7fSBen Coburn // handle lines 110*7d559c7fSBen Coburn for($i = count($lines)-1; $i >= 0; $i--){ 111*7d559c7fSBen Coburn $rec = _handleRecent($lines[$i], $ns, $flags); 112*7d559c7fSBen Coburn if($rec !== false) { 113*7d559c7fSBen Coburn if(--$first >= 0) continue; // skip first entries 114*7d559c7fSBen Coburn $recent[] = $rec; 115*7d559c7fSBen Coburn $count++; 116*7d559c7fSBen Coburn // break when we have enough entries 117*7d559c7fSBen Coburn if($count >= $num){ break; } 118*7d559c7fSBen Coburn } 119*7d559c7fSBen Coburn } 120*7d559c7fSBen Coburn 121*7d559c7fSBen Coburn return $recent; 122*7d559c7fSBen Coburn} 123*7d559c7fSBen Coburn 124*7d559c7fSBen Coburn/** 125*7d559c7fSBen Coburn * Internal function used by getRecents 126*7d559c7fSBen Coburn * 127*7d559c7fSBen Coburn * don't call directly 128*7d559c7fSBen Coburn * 129*7d559c7fSBen Coburn * @see getRecents() 130*7d559c7fSBen Coburn * @author Andreas Gohr <andi@splitbrain.org> 131*7d559c7fSBen Coburn * @author Ben Coburn <btcoburn@silicodon.net> 132*7d559c7fSBen Coburn */ 133*7d559c7fSBen Coburnfunction _handleRecent($line,$ns,$flags){ 134*7d559c7fSBen Coburn static $seen = array(); //caches seen pages and skip them 135*7d559c7fSBen Coburn if(empty($line)) return false; //skip empty lines 136*7d559c7fSBen Coburn 137*7d559c7fSBen Coburn // split the line into parts 138*7d559c7fSBen Coburn $recent = parseChangelogLine($line); 139*7d559c7fSBen Coburn if ($recent===false) { return false; } 140*7d559c7fSBen Coburn 141*7d559c7fSBen Coburn // skip seen ones 142*7d559c7fSBen Coburn if(isset($seen[$recent['id']])) return false; 143*7d559c7fSBen Coburn 144*7d559c7fSBen Coburn // skip minors 145*7d559c7fSBen Coburn if($recent['type']==='e' && ($flags & RECENTS_SKIP_MINORS)) return false; 146*7d559c7fSBen Coburn 147*7d559c7fSBen Coburn // remember in seen to skip additional sights 148*7d559c7fSBen Coburn $seen[$recent['id']] = 1; 149*7d559c7fSBen Coburn 150*7d559c7fSBen Coburn // check if it's a hidden page 151*7d559c7fSBen Coburn if(isHiddenPage($recent['id'])) return false; 152*7d559c7fSBen Coburn 153*7d559c7fSBen Coburn // filter namespace 154*7d559c7fSBen Coburn if (($ns) && (strpos($recent['id'],$ns.':') !== 0)) return false; 155*7d559c7fSBen Coburn 156*7d559c7fSBen Coburn // exclude subnamespaces 157*7d559c7fSBen Coburn if (($flags & RECENTS_SKIP_SUBSPACES) && (getNS($recent['id']) != $ns)) return false; 158*7d559c7fSBen Coburn 159*7d559c7fSBen Coburn // check ACL 160*7d559c7fSBen Coburn if (auth_quickaclcheck($recent['id']) < AUTH_READ) return false; 161*7d559c7fSBen Coburn 162*7d559c7fSBen Coburn // check existance 163*7d559c7fSBen Coburn if((!@file_exists(wikiFN($recent['id']))) && ($flags & RECENTS_SKIP_DELETED)) return false; 164*7d559c7fSBen Coburn 165*7d559c7fSBen Coburn return $recent; 166*7d559c7fSBen Coburn} 167*7d559c7fSBen Coburn 168*7d559c7fSBen Coburn/** 169*7d559c7fSBen Coburn * Get the changelog information for a specific page id 170*7d559c7fSBen Coburn * and revision (timestamp). Adjacent changelog lines 171*7d559c7fSBen Coburn * are optimistically parsed and cached to speed up 172*7d559c7fSBen Coburn * consecutive calls to getRevisionInfo. For large 173*7d559c7fSBen Coburn * changelog files, only the chunk containing the 174*7d559c7fSBen Coburn * requested changelog line is read. 175*7d559c7fSBen Coburn * 176*7d559c7fSBen Coburn * @author Ben Coburn <btcoburn@silicodon.net> 177*7d559c7fSBen Coburn */ 178*7d559c7fSBen Coburnfunction getRevisionInfo($id, $rev, $chunk_size=8192) { 179*7d559c7fSBen Coburn global $cache_revinfo; 180*7d559c7fSBen Coburn $cache =& $cache_revinfo; 181*7d559c7fSBen Coburn if (!isset($cache[$id])) { $cache[$id] = array(); } 182*7d559c7fSBen Coburn $rev = max($rev, 0); 183*7d559c7fSBen Coburn 184*7d559c7fSBen Coburn // check if it's already in the memory cache 185*7d559c7fSBen Coburn if (isset($cache[$id]) && isset($cache[$id][$rev])) { 186*7d559c7fSBen Coburn return $cache[$id][$rev]; 187*7d559c7fSBen Coburn } 188*7d559c7fSBen Coburn 189*7d559c7fSBen Coburn $file = metaFN($id, '.changes'); 190*7d559c7fSBen Coburn if (!@file_exists($file)) { return false; } 191*7d559c7fSBen Coburn if (filesize($file)<$chunk_size || $chunk_size==0) { 192*7d559c7fSBen Coburn // read whole file 193*7d559c7fSBen Coburn $lines = file($file); 194*7d559c7fSBen Coburn if ($lines===false) { return false; } 195*7d559c7fSBen Coburn } else { 196*7d559c7fSBen Coburn // read by chunk 197*7d559c7fSBen Coburn $fp = fopen($file, 'rb'); // "file pointer" 198*7d559c7fSBen Coburn if ($fp===false) { return false; } 199*7d559c7fSBen Coburn $head = 0; 200*7d559c7fSBen Coburn fseek($fp, 0, SEEK_END); 201*7d559c7fSBen Coburn $tail = ftell($fp); 202*7d559c7fSBen Coburn $finger = 0; 203*7d559c7fSBen Coburn $finger_rev = 0; 204*7d559c7fSBen Coburn 205*7d559c7fSBen Coburn // find chunk 206*7d559c7fSBen Coburn while ($tail-$head>$chunk_size) { 207*7d559c7fSBen Coburn $finger = $head+floor(($tail-$head)/2.0); 208*7d559c7fSBen Coburn fseek($fp, $finger); 209*7d559c7fSBen Coburn fgets($fp); // slip the finger forward to a new line 210*7d559c7fSBen Coburn $finger = ftell($fp); 211*7d559c7fSBen Coburn $tmp = fgets($fp); // then read at that location 212*7d559c7fSBen Coburn $tmp = parseChangelogLine($tmp); 213*7d559c7fSBen Coburn $finger_rev = $tmp['date']; 214*7d559c7fSBen Coburn if ($finger==$head || $finger==$tail) { break; } 215*7d559c7fSBen Coburn if ($finger_rev>$rev) { 216*7d559c7fSBen Coburn $tail = $finger; 217*7d559c7fSBen Coburn } else { 218*7d559c7fSBen Coburn $head = $finger; 219*7d559c7fSBen Coburn } 220*7d559c7fSBen Coburn } 221*7d559c7fSBen Coburn 222*7d559c7fSBen Coburn if ($tail-$head<1) { 223*7d559c7fSBen Coburn // cound not find chunk, assume requested rev is missing 224*7d559c7fSBen Coburn fclose($fp); 225*7d559c7fSBen Coburn return false; 226*7d559c7fSBen Coburn } 227*7d559c7fSBen Coburn 228*7d559c7fSBen Coburn // read chunk 229*7d559c7fSBen Coburn $chunk = ''; 230*7d559c7fSBen Coburn $chunk_size = max($tail-$head, 0); // found chunk size 231*7d559c7fSBen Coburn $got = 0; 232*7d559c7fSBen Coburn fseek($fp, $head); 233*7d559c7fSBen Coburn while ($got<$chunk_size && !feof($fp)) { 234*7d559c7fSBen Coburn $tmp = fread($fp, max($chunk_size-$got, 0)); 235*7d559c7fSBen Coburn if ($tmp===false) { break; } //error state 236*7d559c7fSBen Coburn $got += strlen($tmp); 237*7d559c7fSBen Coburn $chunk .= $tmp; 238*7d559c7fSBen Coburn } 239*7d559c7fSBen Coburn $lines = explode("\n", $chunk); 240*7d559c7fSBen Coburn array_pop($lines); // remove trailing newline 241*7d559c7fSBen Coburn fclose($fp); 242*7d559c7fSBen Coburn } 243*7d559c7fSBen Coburn 244*7d559c7fSBen Coburn // parse and cache changelog lines 245*7d559c7fSBen Coburn foreach ($lines as $value) { 246*7d559c7fSBen Coburn $tmp = parseChangelogLine($value); 247*7d559c7fSBen Coburn if ($tmp!==false) { 248*7d559c7fSBen Coburn $cache[$id][$tmp['date']] = $tmp; 249*7d559c7fSBen Coburn } 250*7d559c7fSBen Coburn } 251*7d559c7fSBen Coburn if (!isset($cache[$id][$rev])) { return false; } 252*7d559c7fSBen Coburn return $cache[$id][$rev]; 253*7d559c7fSBen Coburn} 254*7d559c7fSBen Coburn 255*7d559c7fSBen Coburn/** 256*7d559c7fSBen Coburn * Return a list of page revisions numbers 257*7d559c7fSBen Coburn * Does not guarantee that the revision exists in the attic, 258*7d559c7fSBen Coburn * only that a line with the date exists in the changelog. 259*7d559c7fSBen Coburn * By default the current revision is skipped. 260*7d559c7fSBen Coburn * 261*7d559c7fSBen Coburn * id: the page of interest 262*7d559c7fSBen Coburn * first: skip the first n changelog lines 263*7d559c7fSBen Coburn * num: number of revisions to return 264*7d559c7fSBen Coburn * 265*7d559c7fSBen Coburn * The current revision is automatically skipped when the page exists. 266*7d559c7fSBen Coburn * See $INFO['meta']['last_change'] for the current revision. 267*7d559c7fSBen Coburn * 268*7d559c7fSBen Coburn * For efficiency, the log lines are parsed and cached for later 269*7d559c7fSBen Coburn * calls to getRevisionInfo. Large changelog files are read 270*7d559c7fSBen Coburn * backwards in chunks untill the requested number of changelog 271*7d559c7fSBen Coburn * lines are recieved. 272*7d559c7fSBen Coburn * 273*7d559c7fSBen Coburn * @author Ben Coburn <btcoburn@silicodon.net> 274*7d559c7fSBen Coburn */ 275*7d559c7fSBen Coburnfunction getRevisions($id, $first, $num, $chunk_size=8192) { 276*7d559c7fSBen Coburn global $cache_revinfo; 277*7d559c7fSBen Coburn $cache =& $cache_revinfo; 278*7d559c7fSBen Coburn if (!isset($cache[$id])) { $cache[$id] = array(); } 279*7d559c7fSBen Coburn 280*7d559c7fSBen Coburn $revs = array(); 281*7d559c7fSBen Coburn $lines = array(); 282*7d559c7fSBen Coburn $count = 0; 283*7d559c7fSBen Coburn $file = metaFN($id, '.changes'); 284*7d559c7fSBen Coburn $num = max($num, 0); 285*7d559c7fSBen Coburn $chunk_size = max($chunk_size, 0); 286*7d559c7fSBen Coburn if ($first<0) { $first = 0; } 287*7d559c7fSBen Coburn else if (@file_exists(wikiFN($id))) { 288*7d559c7fSBen Coburn // skip current revision if the page exists 289*7d559c7fSBen Coburn $first = max($first+1, 0); 290*7d559c7fSBen Coburn } 291*7d559c7fSBen Coburn 292*7d559c7fSBen Coburn if (!@file_exists($file)) { return $revs; } 293*7d559c7fSBen Coburn if (filesize($file)<$chunk_size || $chunk_size==0) { 294*7d559c7fSBen Coburn // read whole file 295*7d559c7fSBen Coburn $lines = file($file); 296*7d559c7fSBen Coburn if ($lines===false) { return $revs; } 297*7d559c7fSBen Coburn } else { 298*7d559c7fSBen Coburn // read chunks backwards 299*7d559c7fSBen Coburn $fp = fopen($file, 'rb'); // "file pointer" 300*7d559c7fSBen Coburn if ($fp===false) { return $revs; } 301*7d559c7fSBen Coburn fseek($fp, 0, SEEK_END); 302*7d559c7fSBen Coburn $tail = ftell($fp); 303*7d559c7fSBen Coburn 304*7d559c7fSBen Coburn // chunk backwards 305*7d559c7fSBen Coburn $finger = max($tail-$chunk_size, 0); 306*7d559c7fSBen Coburn while ($count<$num+$first) { 307*7d559c7fSBen Coburn fseek($fp, $finger); 308*7d559c7fSBen Coburn if ($finger>0) { 309*7d559c7fSBen Coburn fgets($fp); // slip the finger forward to a new line 310*7d559c7fSBen Coburn $finger = ftell($fp); 311*7d559c7fSBen Coburn } 312*7d559c7fSBen Coburn 313*7d559c7fSBen Coburn // read chunk 314*7d559c7fSBen Coburn if ($tail<=$finger) { break; } 315*7d559c7fSBen Coburn $chunk = ''; 316*7d559c7fSBen Coburn $read_size = max($tail-$finger, 0); // found chunk size 317*7d559c7fSBen Coburn $got = 0; 318*7d559c7fSBen Coburn while ($got<$read_size && !feof($fp)) { 319*7d559c7fSBen Coburn $tmp = fread($fp, max($read_size-$got, 0)); 320*7d559c7fSBen Coburn if ($tmp===false) { break; } //error state 321*7d559c7fSBen Coburn $got += strlen($tmp); 322*7d559c7fSBen Coburn $chunk .= $tmp; 323*7d559c7fSBen Coburn } 324*7d559c7fSBen Coburn $tmp = explode("\n", $chunk); 325*7d559c7fSBen Coburn array_pop($tmp); // remove trailing newline 326*7d559c7fSBen Coburn 327*7d559c7fSBen Coburn // combine with previous chunk 328*7d559c7fSBen Coburn $count += count($tmp); 329*7d559c7fSBen Coburn $lines = array_merge($tmp, $lines); 330*7d559c7fSBen Coburn 331*7d559c7fSBen Coburn // next chunk 332*7d559c7fSBen Coburn if ($finger==0) { break; } // already read all the lines 333*7d559c7fSBen Coburn else { 334*7d559c7fSBen Coburn $tail = $finger; 335*7d559c7fSBen Coburn $finger = max($tail-$chunk_size, 0); 336*7d559c7fSBen Coburn } 337*7d559c7fSBen Coburn } 338*7d559c7fSBen Coburn fclose($fp); 339*7d559c7fSBen Coburn } 340*7d559c7fSBen Coburn 341*7d559c7fSBen Coburn // skip parsing extra lines 342*7d559c7fSBen Coburn $num = max(min(count($lines)-$first, $num), 0); 343*7d559c7fSBen Coburn if ($first>0 && $num>0) { $lines = array_slice($lines, max(count($lines)-$first-$num, 0), $num); } 344*7d559c7fSBen Coburn else if ($first>0 && $num==0) { $lines = array_slice($lines, 0, max(count($lines)-$first, 0)); } 345*7d559c7fSBen Coburn else if ($first==0 && $num>0) { $lines = array_slice($lines, max(count($lines)-$num, 0)); } 346*7d559c7fSBen Coburn 347*7d559c7fSBen Coburn // handle lines in reverse order 348*7d559c7fSBen Coburn for ($i = count($lines)-1; $i >= 0; $i--) { 349*7d559c7fSBen Coburn $tmp = parseChangelogLine($lines[$i]); 350*7d559c7fSBen Coburn if ($tmp!==false) { 351*7d559c7fSBen Coburn $cache[$id][$tmp['date']] = $tmp; 352*7d559c7fSBen Coburn $revs[] = $tmp['date']; 353*7d559c7fSBen Coburn } 354*7d559c7fSBen Coburn } 355*7d559c7fSBen Coburn 356*7d559c7fSBen Coburn return $revs; 357*7d559c7fSBen Coburn} 358*7d559c7fSBen Coburn 359*7d559c7fSBen Coburn 360