Completed
Push — master ( be9660...707803 )
by Georges
16s queued 13s
created

IOHelperTrait::writefile()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 39
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 20
c 0
b 0
f 0
nc 6
nop 3
dl 0
loc 39
rs 9.2888
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 file.
10
 *
11
 * @author Khoa Bui (khoaofgod)  <[email protected]> https://www.phpfastcache.com
12
 * @author Georges.L (Geolim4)  <[email protected]>
13
 *
14
 */
15
declare(strict_types=1);
16
17
namespace Phpfastcache\Core\Pool\IO;
18
19
use Phpfastcache\Core\Item\ExtendedCacheItemInterface;
20
use Phpfastcache\Core\Pool\ExtendedCacheItemPoolInterface;
21
use Phpfastcache\Drivers\Files\Config;
22
use Phpfastcache\Entities\DriverStatistic;
23
use Phpfastcache\Event\EventManagerInterface;
24
use Phpfastcache\Exceptions\PhpfastcacheIOException;
25
use Phpfastcache\Util\Directory;
26
27
28
/**
29
 * Trait IOHelperTrait
30
 * @package phpFastCache\Core\Pool\IO
31
 * @property array $config The configuration array passed via DriverBaseTrait
32
 * @property ExtendedCacheItemInterface[] $itemInstances The item instance passed via CacheItemPoolTrait
33
 * @property EventManagerInterface $eventManager The event manager passed via CacheItemPoolTrait
34
 * @method Config getConfig() Return the config object
35
 * @method bool isPHPModule() Return true if is a php module
36
 * @method string getDriverName() Get the driver name
37
 */
38
trait IOHelperTrait
39
{
40
    /**
41
     * @var array
42
     */
43
    public $tmp = [];
44
45
    /**
46
     * Provide a generic getStats() method
47
     * for files-based drivers
48
     * @return DriverStatistic
49
     * @throws PhpfastcacheIOException
50
     */
51
    public function getStats(): DriverStatistic
52
    {
53
        $stat = new DriverStatistic();
54
        $path = $this->getFilePath(false);
55
56
        if (!is_dir($path)) {
57
            throw new PhpfastcacheIOException("Can't read PATH:" . $path);
58
        }
59
60
        $stat->setData(implode(', ', \array_keys($this->itemInstances)))
61
            ->setRawData(
62
                [
63
                    'tmp' => $this->tmp,
64
                ]
65
            )
66
            ->setSize(Directory::dirSize($path))
67
            ->setInfo('Number of files used to build the cache: ' . Directory::getFileCount($path));
68
69
        return $stat;
70
    }
71
72
    /**
73
     * @param $keyword
74
     * @param bool $skip
75
     * @return string
76
     * @throws PhpfastcacheIOException
77
     */
78
    protected function getFilePath($keyword, $skip = false): string
79
    {
80
        $path = $this->getPath();
81
82
        if ($keyword === false) {
83
            return $path;
84
        }
85
86
        $filename = $this->encodeFilename($keyword);
87
        $folder = \substr($filename, 0, 2) . DIRECTORY_SEPARATOR . \substr($filename, 2, 2);
88
        $path = \rtrim($path, '/\\') . DIRECTORY_SEPARATOR . $folder;
89
90
        /**
91
         * Skip Create Sub Folders;
92
         */
93
        if (!$skip && !\is_dir($path) && @!\mkdir($path, $this->getDefaultChmod(), true) && !\is_dir($path)) {
94
            throw new PhpfastcacheIOException(
95
                'Path "' . $path . '" is not writable, please set a chmod 0777 or any writable permission and make sure to make use of an absolute path !'
96
            );
97
        }
98
99
        return $path . '/' . $filename . '.' . $this->getConfig()->getCacheFileExtension();
100
    }
101
102
    /**
103
     * @param bool $readonly
104
     * @return string
105
     * @throws PhpfastcacheIOException
106
     */
107
    public function getPath($readonly = false): string
108
    {
109
        /**
110
         * Get the base system temporary directory
111
         */
112
        $tmp_dir = \rtrim(\ini_get('upload_tmp_dir') ?: \sys_get_temp_dir(), '\\/') . DIRECTORY_SEPARATOR . 'phpfastcache';
113
114
        /**
115
         * Calculate the security key
116
         */
117
        {
118
            $securityKey = $this->getConfig()->getSecurityKey();
119
            if (!$securityKey || \mb_strtolower($securityKey) === 'auto') {
120
                if (isset($_SERVER['HTTP_HOST'])) {
121
                    $securityKey = \preg_replace('/^www./', '', \strtolower(\str_replace(':', '_', $_SERVER['HTTP_HOST'])));
122
                } else {
123
                    $securityKey = ($this->isPHPModule() ? 'web' : 'cli');
124
                }
125
            }
126
127
            if ($securityKey !== '') {
128
                $securityKey .= '/';
129
            }
130
131
            $securityKey = static::cleanFileName($securityKey);
132
        }
133
134
        /**
135
         * Extends the temporary directory
136
         * with the security key and the driver name
137
         */
138
        $tmp_dir = \rtrim($tmp_dir, '/') . DIRECTORY_SEPARATOR;
139
140
        if (empty($this->getConfig()->getPath())) {
141
            $path = $tmp_dir;
142
        } else {
143
            $path = \rtrim($this->getConfig()->getPath(), '/') . DIRECTORY_SEPARATOR;
144
        }
145
146
        $path_suffix = $securityKey . DIRECTORY_SEPARATOR . $this->getDriverName();
147
        $full_path = Directory::getAbsolutePath($path . $path_suffix);
148
        $full_path_tmp = Directory::getAbsolutePath($tmp_dir . $path_suffix);
149
        $full_path_hash = $this->getConfig()->getDefaultFileNameHashFunction()($full_path);
150
151
        /**
152
         * In readonly mode we only attempt
153
         * to verify if the directory exists
154
         * or not, if it does not then we
155
         * return the temp dir
156
         */
157
        if ($readonly === true) {
158
            if ($this->getConfig()->isAutoTmpFallback() && (!@\file_exists($full_path) || !@\is_writable($full_path))) {
159
                return $full_path_tmp;
160
            }
161
            return $full_path;
162
        }
163
164
        if (!isset($this->tmp[$full_path_hash]) || (!@\file_exists($full_path) || !@\is_writable($full_path))) {
165
            if (!@\file_exists($full_path)) {
166
                if (@mkdir($full_path, $this->getDefaultChmod(), true) === false && !\is_dir($full_path)) {
167
                    throw new PhpfastcacheIOException('The directory ' . $full_path . ' could not be created.');
168
                }
169
            } else {
170
                if (!@\is_writable($full_path)) {
171
                    if (!@\chmod($full_path, $this->getDefaultChmod()) && $this->getConfig()->isAutoTmpFallback()) {
172
                        /**
173
                         * Switch back to tmp dir
174
                         * again if the path is not writable
175
                         */
176
                        $full_path = $full_path_tmp;
177
                        if (!@\file_exists($full_path)) {
178
                            if (@\mkdir($full_path, $this->getDefaultChmod(), true) && !\is_dir($full_path)) {
179
                                throw new PhpfastcacheIOException('The directory ' . $full_path . ' could not be created.');
180
                            }
181
                        }
182
                    }
183
                }
184
            }
185
186
            /**
187
             * In case there is no directory
188
             * writable including the temporary
189
             * one, we must throw an exception
190
             */
191
            if (!@\file_exists($full_path) || !@\is_writable($full_path)) {
192
                throw new PhpfastcacheIOException(
193
                    'Path "' . $full_path . '" is not writable, please set a chmod 0777 or any writable permission and make sure to make use of an absolute path !'
194
                );
195
            }
196
197
            $this->tmp[$full_path_hash] = $full_path;
198
            $this->htaccessGen($full_path, $this->getConfig()->isValidOption('htaccess') ? $this->getConfig()->getHtaccess() : false);
199
        }
200
201
        return realpath($full_path);
202
    }
203
204
    /**
205
     * @param $filename
206
     * @return string
207
     */
208
    protected static function cleanFileName($filename): string
209
    {
210
        $regex = [
211
            '/[\?\[\]\/\\\=\<\>\:\;\,\'\"\&\$\#\*\(\)\|\~\`\!\{\}]/',
212
            '/\.$/',
213
            '/^\./',
214
        ];
215
        $replace = ['-', '', ''];
216
217
        return \trim(\preg_replace($regex, $replace, \trim($filename)), '-');
218
    }
219
220
    /**
221
     * @return int
222
     */
223
    protected function getDefaultChmod(): int
224
    {
225
        if (!$this->getConfig()->getDefaultChmod()) {
226
            return 0777;
227
        }
228
229
        return $this->getConfig()->getDefaultChmod();
230
    }
231
232
    /**
233
     * @param $path
234
     * @param bool $create
235
     * @throws PhpfastcacheIOException
236
     */
237
    protected function htaccessGen($path, $create = true)
238
    {
239
        if ($create === true) {
240
            if (!\is_writable($path)) {
241
                try {
242
                    if (!\chmod($path, 0777)) {
243
                        throw new PhpfastcacheIOException('Chmod failed on : ' . $path);
244
                    }
245
                } catch (PhpfastcacheIOException $e) {
246
                    throw new PhpfastcacheIOException('PLEASE CHMOD ' . $path . ' - 0777 OR ANY WRITABLE PERMISSION!', 0, $e);
247
                }
248
            }
249
250
            if (!\file_exists($path . '/.htaccess')) {
251
                $file = @\fopen($path . '/.htaccess', 'w+b');
252
                if (!$file) {
0 ignored issues
show
introduced by
$file is of type false|resource, thus it always evaluated to false.
Loading history...
253
                    throw new PhpfastcacheIOException('PLEASE CHMOD ' . $path . ' - 0777 OR ANY WRITABLE PERMISSION!');
254
                }
255
                \fwrite(
256
                    $file,
257
                    <<<HTACCESS
258
### This .htaccess is auto-generated by PhpFastCache ###
259
<IfModule mod_authz_host>
260
Require all denied
261
</IfModule>
262
<IfModule !mod_authz_host>
263
Order Allow,Deny
264
Deny from all
265
</IfModule>
266
HTACCESS
267
                );
268
                \fclose($file);
269
            }
270
        }
271
    }
272
273
    /**
274
     * @param $keyword
275
     * @return string
276
     */
277
    protected function encodeFilename($keyword): string
278
    {
279
        return $this->getConfig()->getDefaultFileNameHashFunction()($keyword);
280
    }
281
282
    /**
283
     * @param $file
284
     * @return string
285
     * @throws PhpfastcacheIOException
286
     */
287
    protected function readFile($file): string
288
    {
289
        if (\function_exists('file_get_contents')) {
290
            return (string)\file_get_contents($file);
291
        }
292
293
        $string = '';
294
295
        $file_handle = @\fopen($file, 'rb');
296
        if (!$file_handle) {
0 ignored issues
show
introduced by
$file_handle is of type false|resource, thus it always evaluated to false.
Loading history...
297
            throw new PhpfastcacheIOException("Cannot read file located at: {$file}");
298
        }
299
        while (!\feof($file_handle)) {
300
            $line = \fgets($file_handle);
301
            $string .= $line;
302
        }
303
        \fclose($file_handle);
304
305
        return $string;
306
    }
307
308
    /********************
309
     *
310
     * PSR-6 Extended Methods
311
     *
312
     *******************/
313
314
    /**
315
     * @param string $file
316
     * @param string $data
317
     * @param bool $secureFileManipulation
318
     * @return bool
319
     * @throws PhpfastcacheIOException
320
     */
321
    protected function writefile($file, $data, $secureFileManipulation = false): bool
322
    {
323
        /**
324
         * @eventName CacheWriteFileOnDisk
325
         * @param ExtendedCacheItemPoolInterface $this
326
         * @param string $file
327
         * @param bool $secureFileManipulation
328
         *
329
         */
330
        $this->eventManager->dispatch('CacheWriteFileOnDisk', $this, $file, $secureFileManipulation);
331
332
        if ($secureFileManipulation) {
333
            $tmpFilename = Directory::getAbsolutePath(
334
                dirname($file) . '/tmp_' . $this->getConfig()->getDefaultFileNameHashFunction()(
335
                    \str_shuffle(\uniqid($this->getDriverName(), false))
336
                    . \str_shuffle(\uniqid($this->getDriverName(), false))
337
                )
338
            );
339
340
            $handle = \fopen($tmpFilename, 'w+b');
341
            if (\is_resource($handle)) {
342
                \flock($handle, \LOCK_EX);
343
                $octetWritten = fwrite($handle, $data);
344
                \flock($handle, \LOCK_UN);
345
                \fclose($handle);
346
            }
347
348
            if (!\rename($tmpFilename, $file)) {
349
                throw new PhpfastcacheIOException(\sprintf('Failed to rename %s to %s', $tmpFilename, $file));
350
            }
351
        } else {
352
            $handle = \fopen($file, 'w+b');
353
            if (\is_resource($handle)) {
354
                $octetWritten = \fwrite($handle, $data);
355
                \fclose($handle);
356
            }
357
        }
358
359
        return (bool)($octetWritten ?? false);
360
    }
361
}
362