3
declare(strict_types=1);
6
* This file is part of CodeIgniter 4 framework.
8
* (c) CodeIgniter Foundation <admin@codeigniter.com>
10
* For the full copyright and license information, please view
11
* the LICENSE file that was distributed with this source code.
14
namespace CodeIgniter\Files;
16
use CodeIgniter\Files\Exceptions\FileException;
17
use CodeIgniter\Files\Exceptions\FileNotFoundException;
18
use CodeIgniter\I18n\Time;
20
use ReturnTypeWillChange;
24
* Wrapper for PHP's built-in SplFileInfo, with goodies.
26
* @see \CodeIgniter\Files\FileTest
28
class File extends SplFileInfo
31
* The files size in bytes
40
protected $originalMimeType;
43
* Run our SplFileInfo constructor with an optional verification
44
* that the path is really a file.
46
* @throws FileNotFoundException
48
public function __construct(string $path, bool $checkFile = false)
50
if ($checkFile && ! is_file($path)) {
51
throw FileNotFoundException::forFileNotFound($path);
54
parent::__construct($path);
58
* Retrieve the file size.
60
* Implementations SHOULD return the value stored in the "size" key of
61
* the file in the $_FILES array if available, as PHP calculates this based
62
* on the actual size transmitted. A RuntimeException will be thrown if the file
63
* does not exist or an error occurs.
65
* @return false|int The file size in bytes, or false on failure
67
#[ReturnTypeWillChange]
68
public function getSize()
70
return $this->size ?? ($this->size = parent::getSize());
74
* Retrieve the file size by unit.
76
* @return false|int|string
78
public function getSizeByUnit(string $unit = 'b')
80
return match (strtolower($unit)) {
81
'kb' => number_format($this->getSize() / 1024, 3),
82
'mb' => number_format(($this->getSize() / 1024) / 1024, 3),
83
default => $this->getSize(),
88
* Attempts to determine the file extension based on the trusted
89
* getType() method. If the mime type is unknown, will return null.
91
public function guessExtension(): ?string
93
// naively get the path extension using pathinfo
94
$pathinfo = pathinfo($this->getRealPath() ?: $this->__toString()) + ['extension' => ''];
96
$proposedExtension = $pathinfo['extension'];
98
return Mimes::guessExtensionFromType($this->getMimeType(), $proposedExtension);
102
* Retrieve the media type of the file. SHOULD not use information from
103
* the $_FILES array, but should use other methods to more accurately
104
* determine the type of file, like finfo, or mime_content_type().
106
* @return string The media type we determined it to be.
108
public function getMimeType(): string
110
if (! function_exists('finfo_open')) {
111
return $this->originalMimeType ?? 'application/octet-stream'; // @codeCoverageIgnore
114
$finfo = finfo_open(FILEINFO_MIME_TYPE);
115
$mimeType = finfo_file($finfo, $this->getRealPath() ?: $this->__toString());
122
* Generates a random names based on a simple hash and the time, with
123
* the correct file extension attached.
125
public function getRandomName(): string
127
$extension = $this->getExtension();
128
$extension = empty($extension) ? '' : '.' . $extension;
130
return Time::now()->getTimestamp() . '_' . bin2hex(random_bytes(10)) . $extension;
134
* Moves a file to a new location.
138
public function move(string $targetPath, ?string $name = null, bool $overwrite = false)
140
$targetPath = rtrim($targetPath, '/') . '/';
141
$name ??= $this->getBasename();
142
$destination = $overwrite ? $targetPath . $name : $this->getDestination($targetPath . $name);
144
$oldName = $this->getRealPath() ?: $this->__toString();
146
if (! @rename($oldName, $destination)) {
147
$error = error_get_last();
149
throw FileException::forUnableToMove($this->getBasename(), $targetPath, strip_tags($error['message']));
152
@chmod($destination, 0777 & ~umask());
154
return new self($destination);
158
* Returns the destination path for the move operation where overwriting is not expected.
160
* First, it checks whether the delimiter is present in the filename, if it is, then it checks whether the
161
* last element is an integer as there may be cases that the delimiter may be present in the filename.
162
* For the all other cases, it appends an integer starting from zero before the file's extension.
164
public function getDestination(string $destination, string $delimiter = '_', int $i = 0): string
166
if ($delimiter === '') {
170
while (is_file($destination)) {
171
$info = pathinfo($destination);
172
$extension = isset($info['extension']) ? '.' . $info['extension'] : '';
174
if (str_contains($info['filename'], $delimiter)) {
175
$parts = explode($delimiter, $info['filename']);
177
if (is_numeric(end($parts))) {
181
$destination = $info['dirname'] . DIRECTORY_SEPARATOR . implode($delimiter, $parts) . $extension;
183
$destination = $info['dirname'] . DIRECTORY_SEPARATOR . $info['filename'] . $delimiter . ++$i . $extension;
186
$destination = $info['dirname'] . DIRECTORY_SEPARATOR . $info['filename'] . $delimiter . ++$i . $extension;