[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/ChangeLog/ -> ChangeLogTrait.php (source)

   1  <?php
   2  
   3  namespace dokuwiki\ChangeLog;
   4  
   5  use dokuwiki\Utf8\PhpString;
   6  
   7  /**
   8   * Provides methods for handling of changelog
   9   */
  10  trait ChangeLogTrait
  11  {
  12      /**
  13       * Adds an entry to the changelog file
  14       *
  15       * @return array added log line as revision info
  16       */
  17      abstract public function addLogEntry(array $info, $timestamp = null);
  18  
  19      /**
  20       * Parses a changelog line into it's components
  21       *
  22       * @author Ben Coburn <btcoburn@silicodon.net>
  23       *
  24       * @param string $line changelog line
  25       * @return array|bool parsed line or false
  26       */
  27      public static function parseLogLine($line)
  28      {
  29          $info = explode("\t", rtrim($line, "\n"));
  30          if ($info !== false && count($info) > 1) {
  31              return [
  32                  'date'  => (int)$info[0], // unix timestamp
  33                  'ip'    => $info[1], // IPv4 address (127.0.0.1)
  34                  'type'  => $info[2], // log line type
  35                  'id'    => $info[3], // page id
  36                  'user'  => $info[4], // user name
  37                  'sum'   => $info[5], // edit summary (or action reason)
  38                  'extra' => $info[6], // extra data (varies by line type)
  39                  'sizechange' => (isset($info[7]) && $info[7] !== '') ? (int)$info[7] : null, //
  40              ];
  41          } else {
  42              return false;
  43          }
  44      }
  45  
  46      /**
  47       * Build a changelog line from it's components
  48       *
  49       * @param array $info Revision info structure
  50       * @param int $timestamp log line date (optional)
  51       * @return string changelog line
  52       */
  53      public static function buildLogLine(array &$info, $timestamp = null)
  54      {
  55          $strip = ["\t", "\n"];
  56          $entry = array(
  57              'date'  => $timestamp ?? $info['date'],
  58              'ip'    => $info['ip'],
  59              'type'  => str_replace($strip, '', $info['type']),
  60              'id'    => $info['id'],
  61              'user'  => $info['user'],
  62              'sum'   => PhpString::substr(str_replace($strip, '', $info['sum']), 0, 255),
  63              'extra' => str_replace($strip, '', $info['extra']),
  64              'sizechange' => $info['sizechange'],
  65          );
  66          $info = $entry;
  67          return implode("\t", $entry) ."\n";
  68      }
  69  
  70      /**
  71       * Returns path to changelog
  72       *
  73       * @return string path to file
  74       */
  75      abstract protected function getChangelogFilename();
  76  
  77      /**
  78       * Checks if the ID has old revisions
  79       * @return boolean
  80       */
  81      public function hasRevisions()
  82      {
  83          $logfile = $this->getChangelogFilename();
  84          return file_exists($logfile);
  85      }
  86  
  87  
  88      /** @var int */
  89      protected $chunk_size;
  90  
  91      /**
  92       * Set chunk size for file reading
  93       * Chunk size zero let read whole file at once
  94       *
  95       * @param int $chunk_size maximum block size read from file
  96       */
  97      public function setChunkSize($chunk_size)
  98      {
  99          if (!is_numeric($chunk_size)) $chunk_size = 0;
 100  
 101          $this->chunk_size = (int)max($chunk_size, 0);
 102      }
 103  
 104      /**
 105       * Returns lines from changelog.
 106       * If file larger than $chunk_size, only chunk is read that could contain $rev.
 107       *
 108       * When reference timestamp $rev is outside time range of changelog, readloglines() will return
 109       * lines in first or last chunk, but they obviously does not contain $rev.
 110       *
 111       * @param int $rev revision timestamp
 112       * @return array|false
 113       *     if success returns array(fp, array(changeloglines), $head, $tail, $eof)
 114       *     where fp only defined for chuck reading, needs closing.
 115       *     otherwise false
 116       */
 117      protected function readloglines($rev)
 118      {
 119          $file = $this->getChangelogFilename();
 120  
 121          if (!file_exists($file)) {
 122              return false;
 123          }
 124  
 125          $fp = null;
 126          $head = 0;
 127          $tail = 0;
 128          $eof = 0;
 129  
 130          if (filesize($file) < $this->chunk_size || $this->chunk_size == 0) {
 131              // read whole file
 132              $lines = file($file);
 133              if ($lines === false) {
 134                  return false;
 135              }
 136          } else {
 137              // read by chunk
 138              $fp = fopen($file, 'rb'); // "file pointer"
 139              if ($fp === false) {
 140                  return false;
 141              }
 142              fseek($fp, 0, SEEK_END);
 143              $eof = ftell($fp);
 144              $tail = $eof;
 145  
 146              // find chunk
 147              while ($tail - $head > $this->chunk_size) {
 148                  $finger = $head + intval(($tail - $head) / 2);
 149                  $finger = $this->getNewlinepointer($fp, $finger);
 150                  $tmp = fgets($fp);
 151                  if ($finger == $head || $finger == $tail) {
 152                      break;
 153                  }
 154                  $info = $this->parseLogLine($tmp);
 155                  $finger_rev = $info['date'];
 156  
 157                  if ($finger_rev > $rev) {
 158                      $tail = $finger;
 159                  } else {
 160                      $head = $finger;
 161                  }
 162              }
 163  
 164              if ($tail - $head < 1) {
 165                  // could not find chunk, assume requested rev is missing
 166                  fclose($fp);
 167                  return false;
 168              }
 169  
 170              $lines = $this->readChunk($fp, $head, $tail);
 171          }
 172          return array(
 173              $fp,
 174              $lines,
 175              $head,
 176              $tail,
 177              $eof,
 178          );
 179      }
 180  
 181      /**
 182       * Read chunk and return array with lines of given chunk.
 183       * Has no check if $head and $tail are really at a new line
 184       *
 185       * @param resource $fp resource file pointer
 186       * @param int $head start point chunk
 187       * @param int $tail end point chunk
 188       * @return array lines read from chunk
 189       */
 190      protected function readChunk($fp, $head, $tail)
 191      {
 192          $chunk = '';
 193          $chunk_size = max($tail - $head, 0); // found chunk size
 194          $got = 0;
 195          fseek($fp, $head);
 196          while ($got < $chunk_size && !feof($fp)) {
 197              $tmp = @fread($fp, max(min($this->chunk_size, $chunk_size - $got), 0));
 198              if ($tmp === false) { //error state
 199                  break;
 200              }
 201              $got += strlen($tmp);
 202              $chunk .= $tmp;
 203          }
 204          $lines = explode("\n", $chunk);
 205          array_pop($lines); // remove trailing newline
 206          return $lines;
 207      }
 208  
 209      /**
 210       * Set pointer to first new line after $finger and return its position
 211       *
 212       * @param resource $fp file pointer
 213       * @param int $finger a pointer
 214       * @return int pointer
 215       */
 216      protected function getNewlinepointer($fp, $finger)
 217      {
 218          fseek($fp, $finger);
 219          $nl = $finger;
 220          if ($finger > 0) {
 221              fgets($fp); // slip the finger forward to a new line
 222              $nl = ftell($fp);
 223          }
 224          return $nl;
 225      }
 226  
 227      /**
 228       * Returns the next lines of the changelog  of the chunk before head or after tail
 229       *
 230       * @param resource $fp file pointer
 231       * @param int $head position head of last chunk
 232       * @param int $tail position tail of last chunk
 233       * @param int $direction positive forward, negative backward
 234       * @return array with entries:
 235       *    - $lines: changelog lines of read chunk
 236       *    - $head: head of chunk
 237       *    - $tail: tail of chunk
 238       */
 239      protected function readAdjacentChunk($fp, $head, $tail, $direction)
 240      {
 241          if (!$fp) return array(array(), $head, $tail);
 242  
 243          if ($direction > 0) {
 244              //read forward
 245              $head = $tail;
 246              $tail = $head + intval($this->chunk_size * (2 / 3));
 247              $tail = $this->getNewlinepointer($fp, $tail);
 248          } else {
 249              //read backward
 250              $tail = $head;
 251              $head = max($tail - $this->chunk_size, 0);
 252              while (true) {
 253                  $nl = $this->getNewlinepointer($fp, $head);
 254                  // was the chunk big enough? if not, take another bite
 255                  if ($nl > 0 && $tail <= $nl) {
 256                      $head = max($head - $this->chunk_size, 0);
 257                  } else {
 258                      $head = $nl;
 259                      break;
 260                  }
 261              }
 262          }
 263  
 264          //load next chunk
 265          $lines = $this->readChunk($fp, $head, $tail);
 266          return array($lines, $head, $tail);
 267      }
 268  
 269  }