xref: /dokuwiki/inc/ChangeLog/ChangeLogTrait.php (revision 1d11f1d3396b6d0e55489e568b4bb7028ff9f172)
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