Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added overwrite option to UploadedFile->move(), Issue #275 #288

Merged
merged 1 commit into from
Oct 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 48 additions & 3 deletions system/HTTP/Files/UploadedFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,13 @@ public function __construct(string $path, string $originalName, string $mimeType
*
* @param string $targetPath Path to which to move the uploaded file.
* @param string $name the name to rename the file to.
* @param bool $overwrite State for indicating whether to overwrite the previously generated file with the same name or not.
*
* @throws \InvalidArgumentException if the $path specified is invalid.
* @throws \RuntimeException on any error during the move operation.
* @throws \RuntimeException on the second or subsequent call to the method.
*/
public function move(string $targetPath, string $name = null)
public function move(string $targetPath, string $name = null, bool $overwrite = false)
{
if ($this->hasMoved)
{
Expand All @@ -171,8 +172,9 @@ public function move(string $targetPath, string $name = null)

$targetPath = rtrim($targetPath, '/').'/';
$name = is_null($name) ? $this->getName() : $name;
$destination = $overwrite ? $this->getDestination($targetPath.$name) : $targetPath.$name;

if (! @move_uploaded_file($this->path, $targetPath.$name))
if (! @move_uploaded_file($this->path, $destination))
{
$error = error_get_last();
throw new \RuntimeException(sprintf('Could not move file %s to %s (%s)', basename($this->path), $targetPath, strip_tags($error['message'])));
Expand Down Expand Up @@ -418,6 +420,49 @@ public function isValid(): bool
return is_uploaded_file($this->path) && $this->error === UPLOAD_ERR_OK;
}

//--------------------------------------------------------------------
//--------------------------------------------------------------------

/**
* Returns the destination path for the move operation where overwriting is not expected.
*
* First, it checks whether the delimiter is present in the filename, if it is, then it checks whether the
* last element is an integer as there may be cases that the delimiter may be present in the filename.
* For the all other cases, it appends an integer starting from zero before the file's extension.
*
* @param string $destination
* @param string $delimiter
* @param int $i
*
* @return string
*/
public function getDestination(string $destination, string $delimiter = '_', int $i = 0): string
{
while (file_exists($destination))
{
$info = pathinfo($destination);
if (strpos($info['filename'], $delimiter) !== false)
{
$parts = explode($delimiter, $info['filename']);
if (is_numeric(end($parts)))
{
$i = end($parts);
array_pop($parts);
array_push($parts, ++$i);
$destination = $info['dirname'] . '/' . implode($delimiter, $parts) . '.' . $info['extension'];
}
else
{
$destination = $info['dirname'] . '/' . $info['filename'] . $delimiter . ++$i . '.' . $info['extension'];
}
}
else
{
$destination = $info['dirname'] . '/' . $info['filename'] . $delimiter . ++$i . '.' . $info['extension'];
}
}
return $destination;
}

//--------------------------------------------------------------------

}
17 changes: 17 additions & 0 deletions system/HTTP/Files/UploadedFileInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,23 @@ public function getClientType(): string;
*/
public function isValid(): bool;

//--------------------------------------------------------------------

/**
* Returns the destination path for the move operation where overwriting is not expected.
*
* First, it checks whether the delimiter is present in the filename, if it is, then it checks whether the
* last element is an integer as there may be cases that the delimiter may be present in the filename.
* For the all other cases, it appends an integer starting from zero before the file's extension.
*
* @param string $destination
* @param string $delimiter
* @param int $i
*
* @return string
*/
public function getDestination(string $destination, string $delimiter = '_', int $i = 0): string;

//--------------------------------------------------------------------

}
138 changes: 138 additions & 0 deletions tests/system/HTTP/Files/FileCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -278,5 +278,143 @@ public function testErrorString()
$this->assertEquals($expected, $file->getErrorString());
}

//--------------------------------------------------------------------

/**
* @group move-file
*/
public function testMoveWhereOverwriteIsFalseWithMultipleFilesWithSameName()
{
$finalFilename = 'fileA';

$_FILES = [
'userfile1' => [
'name' => $finalFilename . '.txt',
'type' => 'text/plain',
'size' => 124,
'tmp_name' => '/tmp/fileA.txt',
'error' => 0
],
'userfile2' => [
'name' => 'fileA.txt',
'type' => 'text/csv',
'size' => 248,
'tmp_name' => '/tmp/fileB.txt',
'error' => 0
],
];

$collection = new FileCollection();

$this->assertTrue($collection->hasFile('userfile1'));
$this->assertTrue($collection->hasFile('userfile2'));

$destination = '/tmp/destination/';

// Create the destination if not exists
is_dir($destination) || mkdir($destination, 0777, true);

foreach ($collection->all() as $file) {
$this->assertTrue($file instanceof UploadedFile);
$file->move($destination, $file->getName(), false);
}

$this->assertFileExists($destination . $finalFilename . '.txt');
$this->assertFileNotExists($destination . $finalFilename . '_1.txt');

// Delete the recently created files for the destination above
foreach(glob($destination . "*") as $f) {
unlink($f);
}
// Delete the recently created destination dir
rmdir($destination);
}

//--------------------------------------------------------------------

/**
* @group move-file
*/
public function testMoveWhereOverwriteIsTrueWithMultipleFilesWithSameName()
{
$finalFilename = 'file_with_delimiters_underscore';

$_FILES = [
'userfile1' => [
'name' => $finalFilename . '.txt',
'type' => 'text/plain',
'size' => 124,
'tmp_name' => '/tmp/fileA.txt',
'error' => 0
],
'userfile2' => [
'name' => $finalFilename . '.txt',
'type' => 'text/csv',
'size' => 248,
'tmp_name' => '/tmp/fileB.txt',
'error' => 0
],
'userfile3' => [
'name' => $finalFilename . '.txt',
'type' => 'text/csv',
'size' => 248,
'tmp_name' => '/tmp/fileC.txt',
'error' => 0
],
];

$collection = new FileCollection();

$this->assertTrue($collection->hasFile('userfile1'));
$this->assertTrue($collection->hasFile('userfile2'));
$this->assertTrue($collection->hasFile('userfile3'));

$destination = '/tmp/destination/';

// Create the destination if not exists
is_dir($destination) || mkdir($destination, 0777, true);

foreach ($collection->all() as $file) {
$this->assertTrue($file instanceof UploadedFile);
$file->move($destination, $file->getName(), true);
}

$this->assertFileExists($destination . $finalFilename . '.txt');
$this->assertFileExists($destination . $finalFilename . '_1.txt');
$this->assertFileExists($destination . $finalFilename . '_2.txt');

// Delete the recently created files for the destination above
foreach(glob($destination . "*") as $f) {
unlink($f);
}
// Delete the recently created destination dir
rmdir($destination);
}

//--------------------------------------------------------------------
}

/*
* Overwrite the function so that it will only check whether the file exists or not.
* Original function also checks if the file was uploaded with a POST request.
*
* This overwrite is for testing the move operation.
*/
function is_uploaded_file($filename)
{
if (! file_exists($filename))
{
file_put_contents($filename, 'data');
}
return file_exists($filename);
}

/*
* Overwrite the function so that it just copy without checking the file is an uploaded file.
*
* This overwrite is for testing the move operation.
*/
function move_uploaded_file($filename, $destination)
{
return copy($filename, $destination);
}