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 ability to handle file uploads in a simple way #602

Merged
merged 1 commit into from
Aug 22, 2024
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
49 changes: 12 additions & 37 deletions flight/Engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -896,43 +896,18 @@ public function _jsonp(
}
}

/**
* Downloads a file
*
* @param string $filePath The path to the file to download
* @throws Exception If the file cannot be found
*
* @return void
*/
public function _download(string $filePath): void {
if (file_exists($filePath) === false) {
throw new Exception("$filePath cannot be found.");
}

$fileSize = filesize($filePath);

$mimeType = mime_content_type($filePath);
$mimeType = $mimeType !== false ? $mimeType : 'application/octet-stream';

$response = $this->response();
$response->send();
$response->setRealHeader('Content-Description: File Transfer');
$response->setRealHeader('Content-Type: ' . $mimeType);
$response->setRealHeader('Content-Disposition: attachment; filename="' . basename($filePath) . '"');
$response->setRealHeader('Expires: 0');
$response->setRealHeader('Cache-Control: must-revalidate');
$response->setRealHeader('Pragma: public');
$response->setRealHeader('Content-Length: ' . $fileSize);

// // Clear the output buffer
ob_clean();
flush();

// // Read the file and send it to the output buffer
readfile($filePath);
if(empty(getenv('PHPUNIT_TEST'))) {
exit; // @codeCoverageIgnore
}
/**
* Downloads a file
*
* @param string $filePath The path to the file to download
*
* @throws Exception If the file cannot be found
*
* @return void
*/
public function _download(string $filePath): void
{
$this->response()->downloadFile($filePath);
}

/**
Expand Down
59 changes: 59 additions & 0 deletions flight/net/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -414,4 +414,63 @@ public static function getScheme(): string

return 'http';
}

/**
* Retrieves the array of uploaded files.
*
* @return array<string, array<string,UploadedFile>|array<string,array<string,UploadedFile>>> The array of uploaded files.
*/
public function getUploadedFiles(): array
{
$files = [];
$correctedFilesArray = $this->reArrayFiles($this->files);
foreach ($correctedFilesArray as $keyName => $files) {
foreach ($files as $file) {
$UploadedFile = new UploadedFile(
$file['name'],
$file['type'],
$file['size'],
$file['tmp_name'],
$file['error']
);
if (count($files) > 1) {
$files[$keyName][] = $UploadedFile;
} else {
$files[$keyName] = $UploadedFile;
}
}
}

return $files;
}

/**
* Re-arranges the files in the given files collection.
*
* @param Collection $filesCollection The collection of files to be re-arranged.
*
* @return array<string, array<int, array<string, mixed>>> The re-arranged files collection.
*/
protected function reArrayFiles(Collection $filesCollection): array
{

$fileArray = [];
foreach ($filesCollection as $fileKeyName => $file) {
$isMulti = is_array($file['name']) === true && count($file['name']) > 1;
$fileCount = $isMulti === true ? count($file['name']) : 1;
$fileKeys = array_keys($file);

for ($i = 0; $i < $fileCount; $i++) {
foreach ($fileKeys as $key) {
if ($isMulti === true) {
$fileArray[$fileKeyName][$i][$key] = $file[$key][$i];
} else {
$fileArray[$fileKeyName][$i][$key] = $file[$key];
}
}
}
}

return $fileArray;
}
}
38 changes: 38 additions & 0 deletions flight/net/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -480,4 +480,42 @@ protected function processResponseCallbacks(): void
$this->body = $callback($this->body);
}
}

/**
* Downloads a file.
*
* @param string $filePath The path to the file to be downloaded.
*
* @return void
*/
public function downloadFile(string $filePath): void
{
if (file_exists($filePath) === false) {
throw new Exception("$filePath cannot be found.");
}

$fileSize = filesize($filePath);

$mimeType = mime_content_type($filePath);
$mimeType = $mimeType !== false ? $mimeType : 'application/octet-stream';

$this->send();
$this->setRealHeader('Content-Description: File Transfer');
$this->setRealHeader('Content-Type: ' . $mimeType);
$this->setRealHeader('Content-Disposition: attachment; filename="' . basename($filePath) . '"');
$this->setRealHeader('Expires: 0');
$this->setRealHeader('Cache-Control: must-revalidate');
$this->setRealHeader('Pragma: public');
$this->setRealHeader('Content-Length: ' . $fileSize);

// // Clear the output buffer
ob_clean();
flush();

// // Read the file and send it to the output buffer
readfile($filePath);
if (empty(getenv('PHPUNIT_TEST'))) {
exit; // @codeCoverageIgnore
}
}
}
157 changes: 157 additions & 0 deletions flight/net/UploadedFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php

declare(strict_types=1);

namespace flight\net;

use Exception;

class UploadedFile
{
/**
* @var string $name The name of the uploaded file.
*/
private string $name;

/**
* @var string $mimeType The MIME type of the uploaded file.
*/
private string $mimeType;

/**
* @var int $size The size of the uploaded file in bytes.
*/
private int $size;

/**
* @var string $tmpName The temporary name of the uploaded file.
*/
private string $tmpName;

/**
* @var int $error The error code associated with the uploaded file.
*/
private int $error;

/**
* Constructs a new UploadedFile object.
*
* @param string $name The name of the uploaded file.
* @param string $mimeType The MIME type of the uploaded file.
* @param int $size The size of the uploaded file in bytes.
* @param string $tmpName The temporary name of the uploaded file.
* @param int $error The error code associated with the uploaded file.
*/
public function __construct(string $name, string $mimeType, int $size, string $tmpName, int $error)
{
$this->name = $name;
$this->mimeType = $mimeType;
$this->size = $size;
$this->tmpName = $tmpName;
$this->error = $error;
}

/**
* Retrieves the client-side filename of the uploaded file.
*
* @return string The client-side filename.
*/
public function getClientFilename(): string
{
return $this->name;
}

/**
* Retrieves the media type of the uploaded file as provided by the client.
*
* @return string The media type of the uploaded file.
*/
public function getClientMediaType(): string
{
return $this->mimeType;
}

/**
* Returns the size of the uploaded file.
*
* @return int The size of the uploaded file.
*/
public function getSize(): int
{
return $this->size;
}

/**
* Retrieves the temporary name of the uploaded file.
*
* @return string The temporary name of the uploaded file.
*/
public function getTempName(): string
{
return $this->tmpName;
}

/**
* Get the error code associated with the uploaded file.
*
* @return int The error code.
*/
public function getError(): int
{
return $this->error;
}

/**
* Moves the uploaded file to the specified target path.
*
* @param string $targetPath The path to move the file to.
*
* @return void
*/
public function moveTo(string $targetPath): void
{
if ($this->error !== UPLOAD_ERR_OK) {
throw new Exception($this->getUploadErrorMessage($this->error));
}

$isUploadedFile = is_uploaded_file($this->tmpName) === true;
if (
$isUploadedFile === true
&&
move_uploaded_file($this->tmpName, $targetPath) === false
) {
throw new Exception('Cannot move uploaded file'); // @codeCoverageIgnore
} elseif ($isUploadedFile === false && getenv('PHPUNIT_TEST')) {
rename($this->tmpName, $targetPath);
}
}

/**
* Retrieves the error message for a given upload error code.
*
* @param int $error The upload error code.
*
* @return string The error message.
*/
protected function getUploadErrorMessage(int $error): string
{
switch ($error) {
case UPLOAD_ERR_INI_SIZE:
return 'The uploaded file exceeds the upload_max_filesize directive in php.ini.';
case UPLOAD_ERR_FORM_SIZE:
return 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.';
case UPLOAD_ERR_PARTIAL:
return 'The uploaded file was only partially uploaded.';
case UPLOAD_ERR_NO_FILE:
return 'No file was uploaded.';
case UPLOAD_ERR_NO_TMP_DIR:
return 'Missing a temporary folder.';
case UPLOAD_ERR_CANT_WRITE:
return 'Failed to write file to disk.';
case UPLOAD_ERR_EXTENSION:
return 'A PHP extension stopped the file upload.';
default:
return 'An unknown error occurred. Error code: ' . $error;
}
}
}
Loading