Completed
Push — master ( 4e7427...4a94bb )
by Georges
12s
created

IOHelperTrait::writefile()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

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