PluginProbe
User Access Manager / 2.2.20
User Access Manager v2.2.20
2.3.20 2.3.19 2.3.18 2.3.17 2.3.16 2.3.15 2.3.14 2.3.13 trunk 0.6 0.6.1 0.6.2 0.7 0.7 Beta 0.7.0.1 0.8 0.8.0.1 0.8.0.2 0.9 0.9.1 0.9.1.1 0.9.1.2 0.9.1.3 0.9.1.4 1.0 All 136 releases
← All changes | src/File/FileHandler.php +290 -170 trunk2.2.20 View file →
@@ -1,32 +1,88 @@
1 1 <?php
2 +/**
3 + * FileHandler.php
4 + *
5 + * The FileHandler class file.
6 + *
7 + * PHP versions 5
8 + *
9 + * @author Alexander Schneider <alexanderschneider85@gmail.com>
10 + * @copyright 2008-2017 Alexander Schneider
11 + * @license http://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2
12 + * @version SVN: $id$
13 + * @link http://wordpress.org/extend/plugins/user-access-manager/
14 + */
2 15
3 16 declare(strict_types=1);
4 17
5 18 namespace UserAccessManager\File;
6 19
7 -use JetBrains\PhpStorm\NoReturn;
8 20 use UserAccessManager\Config\MainConfig;
9 21 use UserAccessManager\Config\WordpressConfig;
10 -use UserAccessManager\File\Protection\FileProtectionFactory;
11 -use UserAccessManager\File\Protection\FileProtectionInterface;
12 22 use UserAccessManager\Wrapper\Php;
13 23 use UserAccessManager\Wrapper\Wordpress;
14 24
25 +/**
26 + * Class FileHandler
27 + *
28 + * @package UserAccessManager\FileHandler
29 + */
15 30 class FileHandler
16 31 {
17 - public const X_SEND_FILE_TEST_FILE = 'xSendFileTestFile';
32 + const X_SEND_FILE_TEST_FILE = 'xSendFileTestFile';
18 33
34 + /**
35 + * @var Php
36 + */
37 + private $php;
38 +
39 + /**
40 + * @var Wordpress
41 + */
42 + private $wordpress;
43 +
44 + /**
45 + * @var WordpressConfig
46 + */
47 + private $wordpressConfig;
48 +
49 + /**
50 + * @var MainConfig
51 + */
52 + private $mainConfig;
53 +
54 + /**
55 + * @var FileProtectionFactory
56 + */
57 + private $fileProtectionFactory;
58 +
59 + /**
60 + * FileHandler constructor.
61 + * @param Php $php
62 + * @param Wordpress $wordpress
63 + * @param WordpressConfig $wordpressConfig
64 + * @param MainConfig $mainConfig
65 + * @param FileProtectionFactory $fileProtectionFactory
66 + */
19 67 public function __construct(
20 - private Php $php,
21 - private Wordpress $wordpress,
22 - private WordpressConfig $wordpressConfig,
23 - private MainConfig $mainConfig,
24 - private FileProtectionFactory $fileProtectionFactory
68 + Php $php,
69 + Wordpress $wordpress,
70 + WordpressConfig $wordpressConfig,
71 + MainConfig $mainConfig,
72 + FileProtectionFactory $fileProtectionFactory
25 73 ) {
74 + $this->php = $php;
75 + $this->wordpress = $wordpress;
76 + $this->wordpressConfig = $wordpressConfig;
77 + $this->mainConfig = $mainConfig;
78 + $this->fileProtectionFactory = $fileProtectionFactory;
26 79 }
27 80
28 - private function clearBuffer(): void
81 + /**
82 + * Clears the buffer.
83 + */
84 + private function clearBuffer()
29 85 {
30 86 //prevent '\n' / '0A'
31 87 if ((int) $this->php->iniGet('output_buffering') === 0
32 88 && is_numeric(ob_get_length()) === true
@@ -33,43 +89,67 @@
33 89 ) {
34 90 ob_clean();
35 91 }
36 92
37 - $this->php->flush();
93 + flush();
38 94 }
39 95
40 - private function getFileMimeType(string $file): string
96 + /**
97 + * Returns the file mine type.
98 + * @param string $file
99 + * @return string
100 + */
101 + private function getFileMineType(string $file): string
41 102 {
42 - $explodedFileName = explode('.', basename($file));
43 - $fileExtension = strtolower(array_pop($explodedFileName));
103 + $fileName = basename($file);
104 +
105 + /*
106 + * This only for compatibility
107 + * mime_content_type has been deprecated as the PECL extension file info
108 + * provides the same functionality (and more) in a much cleaner way.
109 + */
110 + $explodedFileName = explode('.', $fileName);
111 + $lastElement = array_pop($explodedFileName);
112 + $fileExt = strtolower($lastElement);
113 +
44 114 $mimeTypes = $this->wordpressConfig->getMimeTypes();
45 115
46 - // The deprecated mime_content_type() is only kept as a fallback for installations without fileinfo.
47 116 if ($this->php->functionExists('finfo_open') === true) {
48 - $fileInfo = $this->php->fInfoOpen(FILEINFO_MIME);
49 - $fileMimeType = $this->php->fInfoFile($fileInfo, $file);
50 - $this->php->fInfoClose($fileInfo);
117 + $fileInfo = finfo_open(FILEINFO_MIME);
118 + $fileMimeType = finfo_file($fileInfo, $file);
119 + finfo_close($fileInfo);
51 120 } elseif ($this->php->functionExists('mime_content_type')) {
52 - $fileMimeType = $this->php->mimeContentType($file);
121 + $fileMimeType = mime_content_type($file);
122 + } elseif (isset($mimeTypes[$fileExt]) === true) {
123 + $fileMimeType = $mimeTypes[$fileExt];
53 124 } else {
54 - $fileMimeType = $mimeTypes[$fileExtension] ?? 'application/octet-stream';
125 + $fileMimeType = 'application/octet-stream';
55 126 }
56 127
57 128 return (string) $fileMimeType;
58 129 }
59 130
60 - private function addDefaultHeader(string $file, bool $isInline): void
131 + /**
132 + * Adds the default header.
133 + * @param string $file
134 + * @param bool $isInline
135 + */
136 + private function addDefaultHeader(string $file, bool $isInline)
61 137 {
62 - $fileMimeType = $this->getFileMimeType($file);
138 + $fileMimeType = $this->getFileMineType($file);
63 139 $contentDisposition = ($isInline === true) ? 'inline' : 'attachment';
64 140 $baseName = str_replace(' ', '_', basename($file));
65 141
66 - $this->php->header('Content-Description: File Transfer');
67 - $this->php->header('Content-Type: ' . $fileMimeType);
68 - $this->php->header("Content-Disposition: $contentDisposition; filename=\"$baseName\"");
142 + header('Content-Description: File Transfer');
143 + header('Content-Type: ' . $fileMimeType);
144 + header("Content-Disposition: {$contentDisposition}; filename=\"{$baseName}\"");
69 145 }
70 146
71 - private function deliverFileViaFopen(string $file): void
147 + /**
148 + * Delivers the file via fopen.
149 + * @param string $file
150 + */
151 + private function deliverFileViaFopen(string $file)
72 152 {
73 153 $handler = fopen($file, 'r');
74 154
75 155 while (feof($handler) === false) {
@@ -80,60 +160,48 @@
80 160 echo $this->php->fread($handler, 1024);
81 161 }
82 162 }
83 163
84 - private function addXSendFileHeader(string $file): bool
164 + /**
165 + * Delivers the file.
166 + * @param string $file
167 + * @param bool $isInline
168 + */
169 + private function deliverFile(string $file, bool $isInline)
85 170 {
86 - if ($this->wordpress->isNginx()) {
87 - // The /uam-files prefix targets a dedicated internal location that bypasses
88 - // UAM's rewrite rules, which would otherwise make the redirect loop.
89 - $uri = '/uam-files' . str_replace(rtrim(ABSPATH, '/'), '', $file);
90 - $this->php->header("X-Accel-Redirect: $uri");
91 -
92 - return true;
93 - }
94 -
95 - if ($this->wordpress->isApacheModuleLoaded('mod_xsendfile')) {
96 - $this->php->header("X-Sendfile: $file");
97 -
98 - return true;
99 - }
100 -
101 - return false;
102 - }
103 -
104 - private function deliverFile(string $file, bool $isInline): void
105 - {
106 - $this->php->header("HTTP/1.1 200 OK");
171 + header("HTTP/1.1 200 OK");
107 172 $downloadType = $this->mainConfig->getDownloadType();
108 173
109 - if ($downloadType === 'xsendfile' && $this->addXSendFileHeader($file) === false) {
110 - // Without server-side sending support the file still has to be delivered by PHP.
111 - $downloadType = 'fopen';
174 + if ($downloadType === 'xsendfile') {
175 + header("X-Sendfile: {$file}");
112 176 }
113 177
114 178 $this->addDefaultHeader($file, $isInline);
115 179
116 - if ($downloadType === 'xsendfile') {
117 - return;
118 - }
180 + if ($downloadType !== 'xsendfile') {
181 + header('Content-Transfer-Encoding: binary');
182 + header('Content-Length: ' . filesize($file));
183 + $this->clearBuffer();
119 184
120 - $this->php->header('Content-Transfer-Encoding: binary');
121 - $this->php->header('Content-Length: ' . filesize($file));
122 - $this->clearBuffer();
123 -
124 - if ($downloadType === 'fopen') {
125 - $this->deliverFileViaFopen($file);
126 - } else {
127 - readfile($file);
185 + if ($downloadType === 'fopen') {
186 + $this->deliverFileViaFopen($file);
187 + } else {
188 + readfile($file);
189 + }
128 190 }
129 191 }
130 192
131 193 /**
132 - * Returns the [start, end] byte offsets of a single HTTP range, or null if the range is invalid.
194 + * Sets the seek start and end.
195 + * @param string $range
196 + * @param int $fileSize
197 + * @param int|null $seekStart
198 + * @param int|null $seekEnd
199 + * @return bool
133 200 */
134 - private function getSeekStartEnd(string $range, int $fileSize): ?array
201 + private function getSeekStartEnd(string $range, int $fileSize, ?int &$seekStart, ?int &$seekEnd): bool
135 202 {
203 + //Figure out download piece from range (if set)
136 204 $seek = explode('-', $range);
137 205 $seekStart = ($seek[0] !== '') ? abs((int) $seek[0]) : null;
138 206 $seekEnd = (isset($seek[1]) === true && $seek[1] !== '') ? abs((int) $seek[1]) : null;
139 207 $maxSize = $fileSize - 1;
@@ -140,16 +208,24 @@
140 208
141 209 if ($seekStart === null) {
142 210 $seekStart = $fileSize - $seekEnd;
143 211 $seekEnd = $maxSize;
212 + } elseif ($seekEnd === null) {
213 + $seekEnd = $maxSize;
144 214 }
145 215
146 - $seekEnd = min($seekEnd ?? $maxSize, $maxSize);
216 + //Start and end based on range (if set), else set defaults also check for invalid ranges.
217 + $seekEnd = min($seekEnd, $maxSize);
147 218
148 - return ($seekStart < $seekEnd) ? [$seekStart, $seekEnd] : null;
219 + return $seekStart < $seekEnd;
149 220 }
150 221
151 - private function readFilePartly($fileHandler, int $bytes): void
222 + /**
223 + * Reads the file partly.
224 + * @param resource $fileHandler
225 + * @param int $bytes
226 + */
227 + private function readFilePartly($fileHandler, int $bytes)
152 228 {
153 229 $bytesLeft = $bytes;
154 230 $bufferSize = 1024;
155 231
@@ -165,106 +241,126 @@
165 241 }
166 242 }
167 243 }
168 244
245 + /**
246 + * Returns the http ranges.
247 + * @param int $fileSize
248 + * @return array
249 + */
169 250 private function getRanges(int $fileSize): array
170 251 {
171 252 $httpRange = explode('=', $_SERVER['HTTP_RANGE']);
172 -
173 - if ($httpRange[0] !== 'bytes') {
174 - return [];
175 - }
176 -
253 + $originRanges = isset($httpRange[1]) === true ? $httpRange[1] : '';
254 + $originRanges = explode(',', $originRanges);
255 + $sizeUnit = $httpRange[0];
177 256 $ranges = [];
178 257
179 - foreach (explode(',', $httpRange[1] ?? '') as $originRange) {
180 - $range = $this->getSeekStartEnd($originRange, $fileSize);
258 + if ($sizeUnit === 'bytes') {
259 + foreach ($originRanges as $originRange) {
260 + if ($this->getSeekStartEnd($originRange, $fileSize, $seekStart, $seekEnd) === false) {
261 + $ranges = [];
262 + break;
263 + }
181 264
182 - if ($range === null) {
183 - return [];
265 + $ranges[] = [$seekStart, $seekEnd];
184 266 }
185 -
186 - $ranges[] = $range;
187 267 }
188 268
189 269 return $ranges;
190 270 }
191 271
272 + /**
273 + * Returns the extra contents.
274 + * @param string $file
275 + * @param array $ranges
276 + * @param int|null $contentLength
277 + * @param string|null $boundary
278 + * @return array
279 + */
192 280 private function getExtraContents(string $file, array $ranges, ?int &$contentLength, ?string &$boundary): array
193 281 {
194 282 $contentLength = 0;
195 283 $extraContents = [];
196 284
197 - if (count($ranges) <= 1) {
198 - return $extraContents;
199 - }
285 + //More than one range is requested?
286 + if (count($ranges) > 1) {
287 + $boundary = 'g45d64df96bmdf4sdgh45hf5';
288 + $fullBoundary = "\r\n--{$boundary}--\r\n";
289 + $fileSize = filesize($file);
290 + $mineType = $this->getFileMineType($file);
200 291
201 - $boundary = 'g45d64df96bmdf4sdgh45hf5';
202 - $fullBoundary = "\r\n--$boundary--\r\n";
203 - $fileSize = filesize($file);
204 - $mimeType = $this->getFileMimeType($file);
292 + //compute content length
293 + foreach ($ranges as $index => $range) {
294 + list($seekStart, $seekEnd) = $range;
295 + $extraContent = $fullBoundary;
296 + $extraContent .= "Content-Type: {$mineType}\r\n";
297 + $extraContent .= "Content-Range: bytes $seekStart-$seekEnd/$fileSize\r\n\r\n";
298 + $extraContents[$index] = $extraContent;
299 + $contentLength += strlen($extraContent) + ($seekEnd - $seekStart + 1);
300 + }
205 301
206 - foreach ($ranges as $index => $range) {
207 - [$seekStart, $seekEnd] = $range;
208 - $extraContent = $fullBoundary
209 - . "Content-Type: $mimeType\r\n"
210 - . "Content-Range: bytes $seekStart-$seekEnd/$fileSize\r\n\r\n";
211 - $extraContents[$index] = $extraContent;
212 - $contentLength += strlen($extraContent) + ($seekEnd - $seekStart + 1);
302 + $contentLength += strlen($fullBoundary);
303 + $extraContents[] = $fullBoundary;
213 304 }
214 305
215 - $contentLength += strlen($fullBoundary);
216 - $extraContents[] = $fullBoundary;
217 -
218 306 return $extraContents;
219 307 }
220 308
221 - private function deliverFilePartial(string $file, bool $isInline): void
309 + /**
310 + * Delivers the file partial.
311 + * @param string $file
312 + * @param bool $isInline
313 + */
314 + private function deliverFilePartial(string $file, bool $isInline)
222 315 {
223 316 $fileSize = filesize($file);
224 317 $ranges = $this->getRanges($fileSize);
225 318
226 - if ($ranges === []) {
227 - $this->php->header('HTTP/1.1 416 Requested Range Not Satisfiable');
228 - $this->php->header("Content-Range: */$fileSize");
319 + if ($ranges !== []) {
320 + $extraContents = $this->getExtraContents($file, $ranges, $contentLength, $boundary);
229 321
230 - return;
231 - }
322 + header('HTTP/1.1 206 Partial Content');
323 + header('Content-Transfer-Encoding: binary');
324 + header('Accept-Ranges: bytes');
232 325
233 - $extraContents = $this->getExtraContents($file, $ranges, $contentLength, $boundary);
326 + if ($extraContents === []) {
327 + $this->addDefaultHeader($file, $isInline);
328 + list($seekStart, $seekEnd) = $ranges[0];
329 + $contentLength = ($seekEnd - $seekStart + 1);
330 + header("Content-Range: bytes {$seekStart}-{$seekEnd}/{$fileSize}");
331 + } else {
332 + header("Content-Type: multipart/x-byteranges; boundary={$boundary}");
333 + }
234 334
235 - $this->php->header('HTTP/1.1 206 Partial Content');
236 - $this->php->header('Content-Transfer-Encoding: binary');
237 - $this->php->header('Accept-Ranges: bytes');
335 + header("Content-Length: {$contentLength}");
336 + $fileHandler = fopen($file, 'r');
238 337
239 - if ($extraContents === []) {
240 - $this->addDefaultHeader($file, $isInline);
241 - [$seekStart, $seekEnd] = $ranges[0];
242 - $contentLength = ($seekEnd - $seekStart + 1);
243 - $this->php->header("Content-Range: bytes $seekStart-$seekEnd/$fileSize");
244 - } else {
245 - $this->php->header("Content-Type: multipart/x-byteranges; boundary=$boundary");
246 - }
338 + foreach ($ranges as $index => $range) {
339 + if (isset($extraContents[$index]) === true) {
340 + echo $extraContents[$index];
341 + }
247 342
248 - $this->php->header("Content-Length: $contentLength");
249 - $fileHandler = fopen($file, 'r');
343 + list($seekStart, $seekEnd) = $ranges[0];
344 + fseek($fileHandler, $seekStart);
345 + $this->readFilePartly($fileHandler, $seekEnd - $seekStart + 1);
346 + }
250 347
251 - foreach ($ranges as $index => $range) {
252 - if (isset($extraContents[$index]) === true) {
253 - echo $extraContents[$index];
348 + if ($extraContents !== []) {
349 + echo end($extraContents);
350 + $this->clearBuffer();
254 351 }
255 -
256 - [$seekStart, $seekEnd] = $range;
257 - $this->php->fseek($fileHandler, $seekStart);
258 - $this->readFilePartly($fileHandler, $seekEnd - $seekStart + 1);
352 + } else {
353 + header('HTTP/1.1 416 Requested Range Not Satisfiable');
354 + header("Content-Range: */$fileSize");
259 355 }
260 -
261 - if ($extraContents !== []) {
262 - echo end($extraContents);
263 - $this->clearBuffer();
264 - }
265 356 }
266 357
358 + /**
359 + * Checks if the file is an inline file
360 + * @param string $file
361 + * @return bool
362 + */
267 363 private function isInlineFile(string $file): bool
268 364 {
269 365 $inlineFiles = array_map('trim', explode(',', (string) $this->mainConfig->getInlineFiles()));
270 366 $map = array_flip($inlineFiles);
@@ -272,39 +368,42 @@
272 368
273 369 return isset($map[$extension]);
274 370 }
275 371
276 - private function isRangeRequest(): bool
372 + /**
373 + * Delivers the content of the requested file.
374 + * @param string $file
375 + * @param bool $isImage
376 + */
377 + public function getFile(string $file, bool $isImage)
277 378 {
278 - return isset($_SERVER['HTTP_RANGE']) === true
279 - && isset($_SERVER['REQUEST_METHOD']) === true
280 - && $_SERVER['REQUEST_METHOD'] === 'GET';
281 - }
379 + //Deliver content
380 + if (file_exists($file) === true) {
381 + $isInline = $isImage === true || $this->isInlineFile($file) === true;
282 382
283 - #[NoReturn]
284 - public function getFile(string $file, bool $isImage): void
285 - {
286 - if (file_exists($file) === false) {
383 + if (isset($_SERVER['HTTP_RANGE']) === true
384 + && isset($_SERVER['REQUEST_METHOD']) === true
385 + && $_SERVER['REQUEST_METHOD'] === 'GET'
386 + ) {
387 + $this->deliverFilePartial($file, $isInline);
388 + } else {
389 + $this->deliverFile($file, $isInline);
390 + }
391 +
392 + $this->php->callExit();
393 + } else {
287 394 $this->wordpress->wpDie(
288 395 TXT_UAM_FILE_NOT_FOUND_ERROR_MESSAGE,
289 396 TXT_UAM_FILE_NOT_FOUND_ERROR_TITLE,
290 397 ['response' => 404]
291 398 );
292 -
293 - return;
294 399 }
295 -
296 - $isInline = $isImage === true || $this->isInlineFile($file) === true;
297 -
298 - if ($this->isRangeRequest() === true) {
299 - $this->deliverFilePartial($file, $isInline);
300 - } else {
301 - $this->deliverFile($file, $isInline);
302 - }
303 -
304 - $this->php->callExit();
305 400 }
306 401
402 + /**
403 + * Returns the current file protection handler.
404 + * @return FileProtectionInterface
405 + */
307 406 private function getCurrentFileProtectionHandler(): FileProtectionInterface
308 407 {
309 408 if ($this->wordpress->isNginx() === true) {
310 409 return $this->fileProtectionFactory->createNginxFileProtection();
@@ -312,8 +411,12 @@
312 411
313 412 return $this->fileProtectionFactory->createApacheFileProtection();
314 413 }
315 414
415 + /**
416 + * Returns the file protection file.
417 + * @return string
418 + */
316 419 public function getFileProtectionFileName(): string
317 420 {
318 421 return $this->getCurrentFileProtectionHandler()->getFileNameWithPath(
319 422 $this->wordpressConfig->getUploadDirectory()
@@ -319,46 +422,63 @@
319 422 $this->wordpressConfig->getUploadDirectory()
320 423 );
321 424 }
322 425
323 - public function createFileProtection(?string $dir = null, ?string $objectType = null): bool
426 + /**
427 + * Creates a protection file.
428 + * @param string $dir The destination directory.
429 + * @param string $objectType The object type.
430 + * @return false
431 + */
432 + public function createFileProtection($dir = null, $objectType = null): bool
324 433 {
325 - $directory = $dir ?? $this->wordpressConfig->getUploadDirectory();
434 + $dir = ($dir === null) ? $this->wordpressConfig->getUploadDirectory() : $dir;
326 435
327 - return $directory !== null
328 - && $this->getCurrentFileProtectionHandler()->create($directory, $objectType);
436 + if ($dir !== null) {
437 + return $this->getCurrentFileProtectionHandler()->create($dir, $objectType);
438 + }
439 +
440 + return false;
329 441 }
330 442
331 - public function deleteFileProtection(?string $dir = null): bool
443 + /**
444 + * Deletes the protection files.
445 + * @param string $dir The destination directory.
446 + * @return false
447 + */
448 + public function deleteFileProtection($dir = null): bool
332 449 {
333 - $directory = $dir ?? $this->wordpressConfig->getUploadDirectory();
450 + $dir = ($dir === null) ? $this->wordpressConfig->getUploadDirectory() : $dir;
334 451
335 - return $directory !== null
336 - && $this->getCurrentFileProtectionHandler()->delete($directory);
337 - }
452 + if ($dir !== null) {
453 + return $this->getCurrentFileProtectionHandler()->delete($dir);
454 + }
338 455
339 - private function getXSendFileTestFilePath(): string
340 - {
341 - return $this->wordpressConfig->getUploadDirectory() . DIRECTORY_SEPARATOR . self::X_SEND_FILE_TEST_FILE;
456 + return false;
342 457 }
343 458
344 - #[NoReturn]
345 - public function deliverXSendFileTestFile(): void
459 + /**
460 + * Delivers a xsendfile test file.
461 + */
462 + public function deliverXSendFileTestFile()
346 463 {
347 - $file = $this->getXSendFileTestFilePath();
464 + $file = $this->wordpressConfig->getUploadDirectory() . DIRECTORY_SEPARATOR . self::X_SEND_FILE_TEST_FILE;
348 465 file_put_contents($file, 'success');
349 466
350 - $this->php->header("X-Sendfile: $file");
351 - $this->php->header('Content-Type: application/octet-stream');
352 - $this->php->header('Content-Disposition: attachment; filename="' . basename($file) . '"');
467 + header("X-Sendfile: {$file}");
468 + header('Content-Type: application/octet-stream');
469 + header('Content-Disposition: attachment; filename="' . basename($file) . '"');
353 470 $this->php->callExit();
354 471 }
355 472
356 - public function removeXSendFileTestFile(): void
473 + /**
474 + * Removes the xsendfile test file if exists.
475 + */
476 + public function removeXSendFileTestFile()
357 477 {
358 - $file = $this->getXSendFileTestFilePath();
478 + $file = $this->wordpressConfig->getUploadDirectory() . DIRECTORY_SEPARATOR . self::X_SEND_FILE_TEST_FILE;
359 479
360 - if ($this->php->isFile($file) === true) {
361 - $this->php->unlink($file);
480 + if (file_exists($file) === true) {
481 + unlink($file);
362 482 }
363 483 }
364 484 }