409 lines
13 KiB
PHP
409 lines
13 KiB
PHP
<?php
|
|
|
|
namespace traits;
|
|
|
|
use Aws\S3\S3Client;
|
|
use classes\wash_certificate_store;
|
|
|
|
trait minio_t
|
|
{
|
|
/**
|
|
* The bucket to store the files in
|
|
* @return string
|
|
*/
|
|
private string $bucket;
|
|
/**
|
|
* The S3 client to interact with the Minio server
|
|
* @return S3Client
|
|
*/
|
|
private s3Client $s3Client;
|
|
|
|
/**
|
|
* List all the files in the bucket
|
|
* @return array
|
|
*/
|
|
public function listFiles(): array
|
|
{
|
|
if ($this->shouldUseLocalTestStorage()) {
|
|
return $this->listLocalTestObjects();
|
|
}
|
|
|
|
$objects = self::getS3Client()->listObjects([
|
|
'Bucket' => self::getBucket()
|
|
]);
|
|
// If there are no files, return an empty array
|
|
if (!isset($objects['Contents'])) {
|
|
return [];
|
|
}
|
|
return $objects['Contents'];
|
|
}
|
|
|
|
/**
|
|
* Returns the S3 client to interact with the Minio server
|
|
* @return S3Client
|
|
*/
|
|
public function getS3Client(): S3Client
|
|
{
|
|
// If the S3 client is not set, create a new one
|
|
if (!isset($this->s3Client)) {
|
|
$this->connect();
|
|
}
|
|
return $this->s3Client;
|
|
}
|
|
|
|
/**
|
|
* Connect to the Minio server
|
|
*/
|
|
private function connect(): self
|
|
{
|
|
$this->s3Client = new S3Client([
|
|
'version' => 'latest',
|
|
'region' => 'us-east-1',
|
|
'endpoint' => $this->getEndpoint(),
|
|
'use_path_style_endpoint' => true,
|
|
'credentials' => [
|
|
'key' => $this->getAccessKey(),
|
|
'secret' => $this->getSecretKey(),
|
|
],
|
|
]);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Returns the endpoint of the Minio server
|
|
* @return string
|
|
*/
|
|
public function getEndpoint(): string
|
|
{
|
|
global $MINIO;
|
|
return is_array($MINIO ?? null) ? (string)($MINIO['endpoint'] ?? '') : '';
|
|
}
|
|
|
|
/**
|
|
* Returns the access key of the Minio server
|
|
* @return string
|
|
*/
|
|
public function getAccessKey(): string
|
|
{
|
|
global $MINIO;
|
|
return is_array($MINIO ?? null) ? (string)($MINIO['access_key'] ?? '') : '';
|
|
}
|
|
|
|
/**
|
|
* Returns the secret key of the Minio server
|
|
* @return string
|
|
*/
|
|
public function getSecretKey(): string
|
|
{
|
|
global $MINIO;
|
|
return is_array($MINIO ?? null) ? (string)($MINIO['secret_key'] ?? '') : '';
|
|
}
|
|
|
|
/**
|
|
* Returns the bucket to store the files in
|
|
* @return string
|
|
*/
|
|
public function getBucket(): string
|
|
{
|
|
return $this->bucket;
|
|
}
|
|
|
|
/**
|
|
* Sets the bucket to store the files in
|
|
* @param string $bucket
|
|
* @return wash_certificate_store|minio_t
|
|
*/
|
|
public function setBucket(string $bucket): self
|
|
{
|
|
$this->bucket = $bucket;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Create a new object in the bucket
|
|
* @param string $key The key of the object (file)
|
|
* @param string $body The content of the object (file)
|
|
* @return bool
|
|
*/
|
|
public function createObject(string $key, string $body): bool
|
|
{
|
|
if ($this->shouldUseLocalTestStorage()) {
|
|
return file_put_contents($this->getLocalTestObjectPath($key), $body) !== false;
|
|
}
|
|
|
|
$result = self::getS3Client()->putObject([
|
|
'Bucket' => self::getBucket(),
|
|
'Key' => $key,
|
|
'Body' => $body
|
|
]);
|
|
return $result['@metadata']['statusCode'] == 200;
|
|
}
|
|
|
|
/**
|
|
* Get the URL of the object in the bucket
|
|
* @param string $key The key of the object (file)
|
|
* @return string
|
|
*/
|
|
public function getObjectUrl(string $key): string
|
|
{
|
|
if ($this->shouldUseLocalTestStorage()) {
|
|
return $this->getLocalTestObjectPath($key);
|
|
}
|
|
|
|
return self::getS3Client()->getObjectUrl(self::getBucket(), $key);
|
|
}
|
|
|
|
/**
|
|
* Generate a presigned URL for the object in the bucket (valid for 20 minutes)
|
|
* @param string $key The key of the object (file)
|
|
* @param int|null $expires The expiration time in seconds (default is not set, which means 20 minutes)
|
|
* @param bool $isUpload Whether the URL is for uploading (true) or downloading (false)
|
|
* @note If $expires is null, it defaults to 1200 seconds (20 minutes).
|
|
* @note If $isUpload is true, the URL will be for uploading the object.
|
|
* @note If $isUpload is false, the URL will be for downloading the object
|
|
* @return string
|
|
*/
|
|
public function getPresignedUrl(string $key, ?int $expires = null, bool $isUpload = false): string
|
|
{
|
|
if ($this->shouldUseLocalTestStorage()) {
|
|
return $this->getLocalTestObjectPath($key);
|
|
}
|
|
|
|
$commandAction = $isUpload ? 'PutObject' : 'GetObject';
|
|
$command = self::getS3Client()->getCommand($commandAction, [
|
|
'Bucket' => self::getBucket(),
|
|
'Key' => $key,
|
|
]);
|
|
$duration_seconds = $expires ?? 1200; // Default to 20 minutes if not specified
|
|
$duration_attribute = '+' . ($duration_seconds / 60) . ' minutes';
|
|
return (string)self::getS3Client()->createPresignedRequest($command, $duration_attribute)->getUri();
|
|
}
|
|
|
|
/**
|
|
* Upload a file to the bucket
|
|
* @param string $key The key of the object (file)
|
|
* @param string $file The path to the file to upload
|
|
* @return bool
|
|
*/
|
|
public function uploadFile(string $key, string $file): bool
|
|
{
|
|
if ($this->shouldUseLocalTestStorage()) {
|
|
return copy($file, $this->getLocalTestObjectPath($key));
|
|
}
|
|
|
|
$result = self::getS3Client()->putObject([
|
|
'Bucket' => self::getBucket(),
|
|
'Key' => $key,
|
|
'SourceFile' => $file
|
|
]);
|
|
return $result['@metadata']['statusCode'] == 200;
|
|
}
|
|
|
|
/**
|
|
* Check if the object exists in the bucket
|
|
* @param string $key The key of the object (file or folder)
|
|
* @return bool
|
|
*/
|
|
public function doesObjectExist(string $key): bool
|
|
{
|
|
if ($this->shouldUseLocalTestStorage()) {
|
|
return file_exists($this->getLocalTestObjectPath($key));
|
|
}
|
|
|
|
return self::getS3Client()->doesObjectExist(self::getBucket(), $key);
|
|
}
|
|
|
|
/**
|
|
* Store temp file from base64 string
|
|
* @param string $base64 The base64 encoded string
|
|
* @param string $fileExtension The file extension (e.g., 'txt', 'jpg')
|
|
* @return string|bool The path to the temporary file if successful, false otherwise
|
|
*/
|
|
public function storeTempFileFromBase64(string $base64, string $fileExtension = 'txt'): string|bool
|
|
{
|
|
// Check if the base64 string is valid
|
|
if (empty($base64)) {
|
|
return false; // Invalid base64 string
|
|
}
|
|
|
|
// Check if the base64 string has a data URI prefix
|
|
if (preg_match('/^data:.*;base64,/', $base64)) {
|
|
$base64 = preg_replace('/^data:.*;base64,/', '', $base64);
|
|
}
|
|
|
|
// Decode the base64 data
|
|
$fileData = base64_decode($base64);
|
|
if ($fileData === false) {
|
|
return false; // Failed to decode base64 data
|
|
}
|
|
// Temporary file path
|
|
$tempFileName = uniqid('temp_file_', true) . '.' . $fileExtension;
|
|
$tempFilePath = '/tmp/' . $tempFileName;
|
|
// Create a temporary file
|
|
$tempFile = fopen($tempFilePath, 'wb');
|
|
if ($tempFile === false) {
|
|
return false; // Failed to create temp file
|
|
}
|
|
|
|
// Write the file data to the temporary file
|
|
if (fwrite($tempFile, $fileData) === false) {
|
|
fclose($tempFile);
|
|
return false; // Failed to write to temp file
|
|
}
|
|
fclose($tempFile);
|
|
// Upload the temporary file to the bucket
|
|
$result = self::uploadFile($tempFileName, $tempFilePath);
|
|
// Clean up the temporary file
|
|
unlink($tempFilePath);
|
|
return $result ? $tempFileName : false;
|
|
}
|
|
|
|
/**
|
|
* Store temp image file from base64 string
|
|
* @param string $base64 The base64 encoded image string (E.g. data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAA)
|
|
* @return string|bool The path to the temporary file if successful, false otherwise
|
|
*/
|
|
public function storeTempImageFromBase64(string $base64): string|bool
|
|
{
|
|
// Check if the base64 string is valid
|
|
if (empty($base64)) {
|
|
return false; // Invalid base64 string
|
|
}
|
|
|
|
// Extract the base64 data from the string if it has a data URI prefix
|
|
$imageData = $base64;
|
|
if (preg_match('/^data:image\/(\w+);base64,/', $base64)) {
|
|
$imageData = preg_replace('/^data:image\/\w+;base64,/', '', $base64);
|
|
}
|
|
|
|
// Decode the base64 data
|
|
$imageData = base64_decode($imageData);
|
|
if ($imageData === false) {
|
|
return false; // Failed to decode base64 data
|
|
}
|
|
// Temporary file path
|
|
$tempFileName = uniqid('temp_image_', true) . '.' . $this->getImageExtension($base64);
|
|
$tempFilePath = '/tmp/' . $tempFileName;
|
|
// Create a temporary file
|
|
$tempFile = fopen($tempFilePath, 'wb');
|
|
if ($tempFile === false) {
|
|
return false; // Failed to create temp file
|
|
}
|
|
|
|
// Write the image data to the temporary file
|
|
if (fwrite($tempFile, $imageData) === false) {
|
|
fclose($tempFile);
|
|
return false; // Failed to write to temp file
|
|
}
|
|
fclose($tempFile);
|
|
// Upload the temporary file to the bucket
|
|
$result = self::uploadFile($tempFileName, $tempFilePath);
|
|
// Clean up the temporary file
|
|
unlink($tempFilePath);
|
|
return $result ? $tempFileName : false;
|
|
}
|
|
|
|
/**
|
|
* Get the image extension from the base64 string
|
|
* @param string $base64 The base64 encoded image string
|
|
* @return string The image extension (e.g., 'jpg', 'png')
|
|
*/
|
|
private function getImageExtension(string $base64): string
|
|
{
|
|
// Check if the base64 string contains a data URL prefix
|
|
if (preg_match('/^data:image\/(\w+);base64,/', $base64, $matches)) {
|
|
// Return the image extension
|
|
switch ($matches[1]) {
|
|
case 'png':
|
|
return 'png';
|
|
case 'gif':
|
|
return 'gif';
|
|
case 'webp':
|
|
return 'webp';
|
|
case 'bmp':
|
|
return 'bmp';
|
|
default:
|
|
return 'jpg'; // Default to jpg if unknown
|
|
}
|
|
}
|
|
// If no match, return a default extension (e.g., 'jpg')
|
|
return 'jpg';
|
|
}
|
|
|
|
private function shouldUseLocalTestStorage(): bool
|
|
{
|
|
if (getenv('RUN_API_TESTS') !== '1') {
|
|
return false;
|
|
}
|
|
|
|
return trim((string)$this->getEndpoint()) === ''
|
|
|| trim((string)$this->getAccessKey()) === ''
|
|
|| trim((string)$this->getSecretKey()) === '';
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array{Key:string}>
|
|
*/
|
|
private function listLocalTestObjects(): array
|
|
{
|
|
$directory = $this->getLocalTestStorageDirectory();
|
|
$objects = [];
|
|
|
|
$iterator = new \RecursiveIteratorIterator(
|
|
new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS)
|
|
);
|
|
|
|
foreach ($iterator as $fileInfo) {
|
|
if (!$fileInfo->isFile()) {
|
|
continue;
|
|
}
|
|
|
|
$pathname = $fileInfo->getPathname();
|
|
$relativePath = substr($pathname, strlen($directory) + 1);
|
|
$objects[] = [
|
|
'Key' => str_replace('\\', '/', $relativePath),
|
|
];
|
|
}
|
|
|
|
return $objects;
|
|
}
|
|
|
|
private function getLocalTestStorageDirectory(): string
|
|
{
|
|
$bucket = preg_replace('/[^a-zA-Z0-9_.-]/', '_', $this->getBucket()) ?: 'default';
|
|
$directory = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR)
|
|
. DIRECTORY_SEPARATOR
|
|
. 'truckwash-test-object-store'
|
|
. DIRECTORY_SEPARATOR
|
|
. $bucket;
|
|
|
|
if (!is_dir($directory)) {
|
|
mkdir($directory, 0777, true);
|
|
}
|
|
|
|
return $directory;
|
|
}
|
|
|
|
private function getLocalTestObjectPath(string $key): string
|
|
{
|
|
$normalizedKey = str_replace('\\', '/', $key);
|
|
$normalizedKey = preg_replace('#(^|/)\\.\\.(?=/|$)#', '', $normalizedKey) ?? $normalizedKey;
|
|
$normalizedKey = ltrim($normalizedKey, '/');
|
|
|
|
if ($normalizedKey === '') {
|
|
$normalizedKey = 'object';
|
|
}
|
|
|
|
$path = $this->getLocalTestStorageDirectory()
|
|
. DIRECTORY_SEPARATOR
|
|
. str_replace('/', DIRECTORY_SEPARATOR, $normalizedKey);
|
|
$directory = dirname($path);
|
|
|
|
if (!is_dir($directory)) {
|
|
mkdir($directory, 0777, true);
|
|
}
|
|
|
|
return $path;
|
|
}
|
|
}
|