Completed
Push — master ( d47b87...74095d )
by Peter
29s queued 11s
created

RenderIndexFileStream::removeOldParts()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3.0123

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 8
cts 9
cp 0.8889
rs 9.8333
c 0
b 0
f 0
cc 3
nc 3
nop 0
crap 3.0123
1
<?php
2
/**
3
 * GpsLab component.
4
 *
5
 * @author    Peter Gribanov <[email protected]>
6
 * @copyright Copyright (c) 2011, Peter Gribanov
7
 * @license   http://opensource.org/licenses/MIT
8
 */
9
10
namespace GpsLab\Component\Sitemap\Stream;
11
12
use GpsLab\Component\Sitemap\Render\SitemapIndexRender;
13
use GpsLab\Component\Sitemap\Stream\Exception\FileAccessException;
14
use GpsLab\Component\Sitemap\Stream\Exception\OverflowException;
15
use GpsLab\Component\Sitemap\Stream\Exception\StreamStateException;
16
use GpsLab\Component\Sitemap\Stream\State\StreamState;
17
use GpsLab\Component\Sitemap\Url\Url;
18
19
class RenderIndexFileStream implements FileStream
20
{
21
    /**
22
     * @var SitemapIndexRender
23
     */
24
    private $render;
25
26
    /**
27
     * @var FileStream
28
     */
29
    private $substream;
30
31
    /**
32
     * @var StreamState
33
     */
34
    private $state;
35
36
    /**
37
     * @var resource|null
38
     */
39
    private $handle;
40
41
    /**
42
     * @var string
43
     */
44
    private $host = '';
45
46
    /**
47
     * @var string
48
     */
49
    private $filename = '';
50
51
    /**
52
     * @var string
53
     */
54
    private $tmp_filename = '';
55
56
    /**
57
     * @var int
58
     */
59
    private $index = 0;
60
61
    /**
62
     * @var int
63
     */
64
    private $counter = 0;
65
66
    /**
67
     * @var bool
68
     */
69
    private $empty_index = true;
70
71
    /**
72
     * @param SitemapIndexRender $render
73
     * @param FileStream         $substream
74
     * @param string             $host
75
     * @param string             $filename
76
     */
77 12
    public function __construct(SitemapIndexRender $render, FileStream $substream, $host, $filename)
78
    {
79 12
        $this->render = $render;
80 12
        $this->substream = $substream;
81 12
        $this->host = $host;
82 12
        $this->filename = $filename;
83 12
        $this->state = new StreamState();
84 12
    }
85
86
    /**
87
     * @return string
88
     */
89 1
    public function getFilename()
90
    {
91 1
        return $this->filename;
92
    }
93
94 9 View Code Duplication
    public function open()
95
    {
96 9
        $this->state->open();
97 9
        $this->substream->open();
98
99 9
        $this->tmp_filename = tempnam(sys_get_temp_dir(), 'sitemap_index');
100 9
        if (($this->handle = @fopen($this->tmp_filename, 'wb')) === false) {
101
            throw FileAccessException::notWritable($this->tmp_filename);
102
        }
103
104 9
        fwrite($this->handle, $this->render->start());
105 9
    }
106
107 12
    public function close()
108
    {
109 12
        $this->state->close();
110 9
        $this->substream->close();
111
112
        // not add empty sitemap part to index
113 9
        if (!$this->empty_index) {
114 5
            $this->addSubStreamFileToIndex();
115
        }
116
117 8
        fwrite($this->handle, $this->render->end());
118 8
        fclose($this->handle);
119
120 8
        $this->moveParts();
121
122
        // move the sitemap index file from the temporary directory to the target
123 8
        if (!rename($this->tmp_filename, $this->filename)) {
124
            unlink($this->tmp_filename);
125
126
            throw FileAccessException::failedOverwrite($this->tmp_filename, $this->filename);
127
        }
128
129 8
        $this->removeOldParts();
130
131 8
        $this->handle = null;
132 8
        $this->tmp_filename = '';
133 8
        $this->counter = 0;
134 8
    }
135
136
    /**
137
     * @param Url $url
138
     */
139 7
    public function push(Url $url)
140
    {
141 7
        if (!$this->state->isReady()) {
142 2
            throw StreamStateException::notReady();
143
        }
144
145
        try {
146 5
            $this->substream->push($url);
147 1
        } catch (OverflowException $e) {
148 1
            $this->substream->close();
149 1
            $this->addSubStreamFileToIndex();
150 1
            $this->substream->open();
151 1
            $this->substream->push($url);
152
        }
153
154 5
        $this->empty_index = false;
155 5
        ++$this->counter;
156 5
    }
157
158 5
    private function addSubStreamFileToIndex()
159
    {
160 5
        $filename = $this->substream->getFilename();
161 5
        $indexed_filename = $this->getIndexPartFilename($filename, ++$this->index);
162
163 5
        if (!file_exists($filename) || ($time = filemtime($filename)) === false) {
164 1
            throw FileAccessException::notReadable($filename);
165
        }
166
167 4
        $last_mod = (new \DateTimeImmutable())->setTimestamp($time);
168
169
        // rename sitemap file to sitemap part
170 4
        $new_filename = sys_get_temp_dir().'/'.$indexed_filename;
171 4
        if (!rename($filename, $new_filename)) {
172
            throw FileAccessException::failedOverwrite($filename, $new_filename);
173
        }
174
175 4
        fwrite($this->handle, $this->render->sitemap($indexed_filename, $last_mod));
0 ignored issues
show
Security Bug introduced by
It seems like $last_mod defined by (new \DateTimeImmutable())->setTimestamp($time) on line 167 can also be of type false; however, GpsLab\Component\Sitemap...pIndexRender::sitemap() does only seem to accept null|object<DateTimeImmutable>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
176 4
    }
177
178
    /**
179
     * @param string $path
180
     * @param int    $index
181
     *
182
     * @return string
183
     */
184 9
    private function getIndexPartFilename($path, $index)
185
    {
186
        // use explode() for correct add index
187
        // sitemap.xml -> sitemap1.xml
188
        // sitemap.xml.gz -> sitemap1.xml.gz
189
190 9
        list($path, $extension) = explode('.', basename($path), 2) + ['', ''];
191
192 9
        return sprintf('%s%s.%s', $path, $index, $extension);
193
    }
194
195
    /**
196
     * @return int
197
     */
198 4
    public function count()
199
    {
200 4
        return $this->counter;
201
    }
202
203
    /**
204
     * Move parts of the sitemap from the temporary directory to the target.
205
     */
206 8
    private function moveParts()
207
    {
208 8
        $filename = $this->substream->getFilename();
209 8
        for ($i = 1; $i <= $this->index; ++$i) {
210 4
            $indexed_filename = $this->getIndexPartFilename($filename, $i);
211 4
            $source = sys_get_temp_dir().'/'.$indexed_filename;
212 4
            $target = dirname($this->filename).'/'.$indexed_filename;
213 4
            if (!rename($source, $target)) {
214
                throw FileAccessException::failedOverwrite($source, $target);
215
            }
216
        }
217 8
    }
218
219
    /**
220
     * Remove old parts of the sitemap from the target directory.
221
     */
222 8
    private function removeOldParts()
223
    {
224 8
        $filename = $this->substream->getFilename();
225 8
        for ($i = $this->index + 1; true; ++$i) {
226 8
            $indexed_filename = $this->getIndexPartFilename($filename, $i);
227 8
            $target = dirname($this->filename).'/'.$indexed_filename;
228 8
            if (file_exists($target)) {
229
                unlink($target);
230
            } else {
231 8
                break;
232
            }
233
        }
234 8
    }
235
}
236