IOHelperTrait::getPath()   B
last analyzed

Complexity

Conditions 7
Paths 6

Size

Total Lines 38
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 17
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 38
rs 8.8333
1
<?php
2
3
/**
4
 *
5
 * This file is part of Phpfastcache.
6
 *
7
 * @license MIT License (MIT)
8
 *
9
 * For full copyright and license information, please see the docs/CREDITS.txt and LICENCE files.
10
 *
11
 * @author Georges.L (Geolim4)  <[email protected]>
12
 * @author Contributors  https://github.com/PHPSocialNetwork/phpfastcache/graphs/contributors
13
 */
14
15
declare(strict_types=1);
16
17
namespace Phpfastcache\Core\Pool\IO;
18
19
use Phpfastcache\Config\IOConfigurationOptionInterface;
20
use Phpfastcache\Core\Pool\TaggableCacheItemPoolTrait;
21
use Phpfastcache\Entities\DriverStatistic;
22
use Phpfastcache\Event\Event;
23
use Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException;
24
use Phpfastcache\Exceptions\PhpfastcacheIOException;
25
use Phpfastcache\Util\Directory;
26
use Phpfastcache\Util\SapiDetector;
27
28
/**
29
 * @method IOConfigurationOptionInterface getConfig()
30
 */
31
trait IOHelperTrait
32
{
33
    use TaggableCacheItemPoolTrait;
34
35
    /**
36
     * @var array<string, string>
37
     */
38
    public array $tmp = [];
39
40
    /**
41
     * Provide a generic getStats() method
42
     * for files-based drivers
43
     * @return DriverStatistic
44
     * @throws PhpfastcacheIOException
45
     * @throws PhpfastcacheInvalidArgumentException
46
     */
47
    public function getStats(): DriverStatistic
48
    {
49
        $stat = new DriverStatistic();
50
        $path = $this->getFilePath(false);
51
52
        if (!is_dir($path)) {
53
            throw new PhpfastcacheIOException("Can't read PATH:" . $path);
54
        }
55
        $stat->setSize(Directory::dirSize($path))
56
            ->setInfo('Number of files used to build the cache: ' . Directory::getFileCount($path))
57
            ->setRawData(
58
                [
59
                    'tmp' => $this->tmp,
60
                ]
61
            );
62
63
        if ($this->getConfig()->isUseStaticItemCaching()) {
64
            $stat->setData(implode(', ', \array_keys($this->itemInstances)));
65
        } else {
66
            $stat->setData('No data available since static item caching option (useStaticItemCaching) is disabled.');
67
        }
68
69
        return $stat;
70
    }
71
72
    /**
73
     * @param string|bool $keyword
74
     * @param bool $skip
75
     * @return string
76
     * @throws PhpfastcacheIOException
77
     * @throws PhpfastcacheInvalidArgumentException
78
     */
79
    protected function getFilePath(string|bool $keyword, bool $skip = false): string
80
    {
81
        $path = $this->getPath();
82
83
        if ($keyword === false) {
84
            return $path;
85
        }
86
87
        $filename = $this->encodeFilename($keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword can also be of type true; however, parameter $keyword of Phpfastcache\Core\Pool\I...Trait::encodeFilename() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

87
        $filename = $this->encodeFilename(/** @scrutinizer ignore-type */ $keyword);
Loading history...
88
        $folder = \substr($filename, 0, 2) . DIRECTORY_SEPARATOR . \substr($filename, 2, 2);
89
        $path = \rtrim($path, '/\\') . DIRECTORY_SEPARATOR . $folder;
90
91
        /**
92
         * Skip Create Sub Folders;
93
         */
94
        if (!$skip && !\is_dir($path) && @!\mkdir($path, $this->getDefaultChmod(), true) && !\is_dir($path)) {
95
            throw new PhpfastcacheIOException(
96
                'Path "' . $path . '" is not writable, please set a chmod 0777 or any writable permission and make sure to make use of an absolute path !'
97
            );
98
        }
99
100
        return $path . \DIRECTORY_SEPARATOR . $filename . '.' . $this->getConfig()->getCacheFileExtension();
101
    }
102
103
    /**
104
     * @param bool $readonly
105
     * @return string
106
     * @throws PhpfastcacheIOException
107
     * @throws PhpfastcacheInvalidArgumentException
108
     */
109
    public function getPath(bool $readonly = false): string
110
    {
111
        $tmpDir = \rtrim(\ini_get('upload_tmp_dir') ?: \sys_get_temp_dir(), '\\/') . DIRECTORY_SEPARATOR . 'phpfastcache';
112
        $httpHost = $this->getConfig()->getSuperGlobalAccessor()('SERVER', 'HTTP_HOST');
113
        $securityKey = $this->buildSecurityKey($httpHost);
114
115
        /**
116
         * Extends the temporary directory
117
         * with the security key and the driver name
118
         */
119
        $tmpDir = \rtrim($tmpDir, '/') . DIRECTORY_SEPARATOR;
120
121
        if (empty($this->getConfig()->getPath())) {
122
            $path = $tmpDir;
123
        } else {
124
            $path = \rtrim($this->getConfig()->getPath(), '/') . DIRECTORY_SEPARATOR;
125
        }
126
127
        $pathSuffix = $securityKey . DIRECTORY_SEPARATOR . $this->getDriverName();
128
        $fullPath = Directory::getAbsolutePath($path . $pathSuffix);
129
        $fullPathTmp = Directory::getAbsolutePath($tmpDir . $pathSuffix);
130
131
        $this->mkdir($fullPath, $fullPathTmp);
132
133
        /**
134
         * In readonly mode we only attempt
135
         * to verify if the directory exists
136
         * or not, if it does not then we
137
         * return the temp dir
138
         */
139
        if ($readonly) {
140
            if ($this->getConfig()->isAutoTmpFallback() && (!@\file_exists($fullPath) || !@\is_writable($fullPath))) {
141
                return $fullPathTmp;
142
            }
143
            return $fullPath;
144
        }
145
146
        return realpath($fullPath);
147
    }
148
149
    protected function buildSecurityKey(?string $httpHost): string
150
    {
151
        $securityKey = $this->getConfig()->getSecurityKey();
152
        if (!$securityKey || \mb_strtolower($securityKey) === 'auto') {
153
            if (isset($httpHost)) {
154
                $securityKey = \preg_replace('/^www./', '', \strtolower(\str_replace(':', '_', $httpHost)));
155
            } else {
156
                $securityKey = (SapiDetector::isWebScript() ? 'web' : 'cli');
157
            }
158
        }
159
160
        if (!empty($securityKey)) {
161
            $securityKey .= '/';
162
        }
163
164
        return static::cleanFileName($securityKey);
165
    }
166
167
    /**
168
     * @throws PhpfastcacheIOException
169
     */
170
    protected function mkdir(string $fullPath, string $fullPathTmp): void
171
    {
172
        $fullPathHash = $this->getConfig()->getDefaultFileNameHashFunction()($fullPath);
173
174
        if (!isset($this->tmp[$fullPathHash]) || (!@\file_exists($fullPath) || !@\is_writable($fullPath))) {
175
            if (!@\file_exists($fullPath)) {
176
                if (@mkdir($fullPath, $this->getDefaultChmod(), true) === false && !\is_dir($fullPath)) {
177
                    throw new PhpfastcacheIOException('The directory ' . $fullPath . ' could not be created.');
178
                }
179
            } elseif (!@\is_writable($fullPath) && !@\chmod($fullPath, $this->getDefaultChmod()) && $this->getConfig()->isAutoTmpFallback()) {
180
                /**
181
                 * Switch back to tmp dir
182
                 * again if the path is not writable
183
                 */
184
                $fullPath = $fullPathTmp;
185
                if (!@\file_exists($fullPath) && @\mkdir($fullPath, $this->getDefaultChmod(), true) && !\is_dir($fullPath)) {
186
                    throw new PhpfastcacheIOException('The directory ' . $fullPath . ' could not be created.');
187
                }
188
            }
189
190
            /**
191
             * In case there is no directory
192
             * writable including the temporary
193
             * one, we must throw an exception
194
             */
195
            if (!@\file_exists($fullPath) || !@\is_writable($fullPath)) {
196
                throw new PhpfastcacheIOException(
197
                    'Path "' . $fullPath . '" is not writable, please set a chmod 0777 or any writable permission and make sure to make use of an absolute path !'
198
                );
199
            }
200
201
            $this->tmp[$fullPathHash] = $fullPath;
202
        }
203
    }
204
205
    /**
206
     * @param string $filename
207
     * @return string
208
     */
209
    protected static function cleanFileName(string $filename): string
210
    {
211
        $regex = [
212
            '/[\?\[\]\/\\\=\<\>\:\;\,\'\"\&\$\#\*\(\)\|\~\`\!\{\}]/',
213
            '/\.$/',
214
            '/^\./',
215
        ];
216
        $replace = ['-', '', ''];
217
218
        return \trim(\preg_replace($regex, $replace, \trim($filename)), '-');
219
    }
220
221
    /**
222
     * @return int
223
     */
224
    protected function getDefaultChmod(): int
225
    {
226
        if (!$this->getConfig()->getDefaultChmod()) {
227
            return 0777;
228
        }
229
230
        return $this->getConfig()->getDefaultChmod();
231
    }
232
233
    /**
234
     * @param string $keyword
235
     * @return string
236
     */
237
    protected function encodeFilename(string $keyword): string
238
    {
239
        return $this->getConfig()->getDefaultFileNameHashFunction()($keyword);
240
    }
241
242
    /**
243
     * @param string $file
244
     * @return string
245
     * @throws PhpfastcacheIOException
246
     */
247
    protected function readFile(string $file): string
248
    {
249
        if (!\is_readable($file)) {
250
            throw new PhpfastcacheIOException("Cannot read file located at: $file");
251
        }
252
        if (\function_exists('file_get_contents')) {
253
            return (string)\file_get_contents($file);
254
        }
255
256
        $string = '';
257
258
        $fileHandle = @\fopen($file, 'rb');
259
        while (!\feof($fileHandle)) {
0 ignored issues
show
Bug introduced by
It seems like $fileHandle can also be of type false; however, parameter $stream of feof() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

259
        while (!\feof(/** @scrutinizer ignore-type */ $fileHandle)) {
Loading history...
260
            $line = \fgets($fileHandle);
0 ignored issues
show
Bug introduced by
It seems like $fileHandle can also be of type false; however, parameter $stream of fgets() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

260
            $line = \fgets(/** @scrutinizer ignore-type */ $fileHandle);
Loading history...
261
            $string .= $line;
262
        }
263
        \fclose($fileHandle);
0 ignored issues
show
Bug introduced by
It seems like $fileHandle can also be of type false; however, parameter $stream of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

263
        \fclose(/** @scrutinizer ignore-type */ $fileHandle);
Loading history...
264
265
        return $string;
266
    }
267
268
    /********************
269
     *
270
     * PSR-6 Extended Methods
271
     *
272
     *******************/
273
274
    /**
275
     * @param string $file
276
     * @param string $data
277
     * @param bool $secureFileManipulation
278
     * @return bool
279
     * @throws PhpfastcacheIOException
280
     * @throws \Exception
281
     */
282
    protected function writeFile(string $file, string $data, bool $secureFileManipulation = false): bool
283
    {
284
        $this->eventManager->dispatch(Event::CACHE_WRITE_FILE_ON_DISK, $this, $file, $secureFileManipulation);
285
286
        if ($secureFileManipulation) {
287
            $tmpFilename = Directory::getAbsolutePath(
288
                dirname($file) . \DIRECTORY_SEPARATOR . 'tmp_' . $this->getConfig()->getDefaultFileNameHashFunction()(
289
                    \bin2hex(\random_bytes(16))
290
                )
291
            ) . '.' . $this->getConfig()->getCacheFileExtension() . \random_int(1000, 9999);
292
293
            $handle = \fopen($tmpFilename, 'w+b');
294
            if (\is_resource($handle)) {
295
                \flock($handle, \LOCK_EX);
296
                $octetWritten = fwrite($handle, $data);
297
                \flock($handle, \LOCK_UN);
298
                \fclose($handle);
299
            }
300
301
            if (!\rename($tmpFilename, $file)) {
302
                throw new PhpfastcacheIOException(\sprintf('Failed to rename %s to %s', $tmpFilename, $file));
303
            }
304
        } else {
305
            $handle = \fopen($file, 'w+b');
306
            if (\is_resource($handle)) {
307
                $octetWritten = \fwrite($handle, $data);
308
                \fclose($handle);
309
            }
310
        }
311
312
        return (bool)($octetWritten ?? false);
313
    }
314
}
315