[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body