[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/ -> changelog.php (source)

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


Generated: Fri Nov 21 01:30:02 2008 Cross-referenced by PHPXref 0.7