[ 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 its components
  21       *
  22       * @param string $line changelog line
  23       * @return array|bool parsed line or false
  24       * @author Ben Coburn <btcoburn@silicodon.net>
  25       *
  26       */
  27      public static function parseLogLine($line)
  28      {
  29          $info = sexplode("\t", rtrim($line, "\n"), 8);
  30          if ($info[3]) { // we need at least the page id to consider it a valid line
  31              return [
  32                  'date' => (int)$info[0], // unix timestamp
  33                  'ip' => $info[1], // IP 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' => ($info[7] != '') ? (int)$info[7] : null, // size difference in bytes
  40              ];
  41          } else {
  42              return false;
  43          }
  44      }
  45  
  46      /**
  47       * Build a changelog line from its 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 = [
  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 = 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 + (int)(($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 [$fp, $lines, $head, $tail, $eof];
 173      }
 174  
 175      /**
 176       * Read chunk and return array with lines of given chunk.
 177       * Has no check if $head and $tail are really at a new line
 178       *
 179       * @param resource $fp resource file pointer
 180       * @param int $head start point chunk
 181       * @param int $tail end point chunk
 182       * @return array lines read from chunk
 183       */
 184      protected function readChunk($fp, $head, $tail)
 185      {
 186          $chunk = '';
 187          $chunk_size = max($tail - $head, 0); // found chunk size
 188          $got = 0;
 189          fseek($fp, $head);
 190          while ($got < $chunk_size && !feof($fp)) {
 191              $tmp = @fread($fp, max(min($this->chunk_size, $chunk_size - $got), 0));
 192              if ($tmp === false) { //error state
 193                  break;
 194              }
 195              $got += strlen($tmp);
 196              $chunk .= $tmp;
 197          }
 198          $lines = explode("\n", $chunk);
 199          array_pop($lines); // remove trailing newline
 200          return $lines;
 201      }
 202  
 203      /**
 204       * Set pointer to first new line after $finger and return its position
 205       *
 206       * @param resource $fp file pointer
 207       * @param int $finger a pointer
 208       * @return int pointer
 209       */
 210      protected function getNewlinepointer($fp, $finger)
 211      {
 212          fseek($fp, $finger);
 213          $nl = $finger;
 214          if ($finger > 0) {
 215              fgets($fp); // slip the finger forward to a new line
 216              $nl = ftell($fp);
 217          }
 218          return $nl;
 219      }
 220  
 221      /**
 222       * Returns the next lines of the changelog  of the chunk before head or after tail
 223       *
 224       * @param resource $fp file pointer
 225       * @param int $head position head of last chunk
 226       * @param int $tail position tail of last chunk
 227       * @param int $direction positive forward, negative backward
 228       * @return array with entries:
 229       *    - $lines: changelog lines of read chunk
 230       *    - $head: head of chunk
 231       *    - $tail: tail of chunk
 232       */
 233      protected function readAdjacentChunk($fp, $head, $tail, $direction)
 234      {
 235          if (!$fp) return [[], $head, $tail];
 236  
 237          if ($direction > 0) {
 238              //read forward
 239              $head = $tail;
 240              $tail = $head + (int)($this->chunk_size * (2 / 3));
 241              $tail = $this->getNewlinepointer($fp, $tail);
 242          } else {
 243              //read backward
 244              $tail = $head;
 245              $head = max($tail - $this->chunk_size, 0);
 246              while (true) {
 247                  $nl = $this->getNewlinepointer($fp, $head);
 248                  // was the chunk big enough? if not, take another bite
 249                  if ($nl > 0 && $tail <= $nl) {
 250                      $head = max($head - $this->chunk_size, 0);
 251                  } else {
 252                      $head = $nl;
 253                      break;
 254                  }
 255              }
 256          }
 257  
 258          //load next chunk
 259          $lines = $this->readChunk($fp, $head, $tail);
 260          return [$lines, $head, $tail];
 261      }
 262  }