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