init
This commit is contained in:
443
app.old/Http/Controllers/Api/MediaController.php
Normal file
443
app.old/Http/Controllers/Api/MediaController.php
Normal file
@@ -0,0 +1,443 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Conductors\MediaConductor;
|
||||
use App\Conductors\MediaJobConductor;
|
||||
use App\Enum\HttpResponseCodes;
|
||||
use App\Http\Requests\MediaRequest;
|
||||
use App\Models\Media;
|
||||
use App\Models\MediaJob;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Laravel\Sanctum\PersonalAccessToken;
|
||||
|
||||
class MediaController extends ApiController
|
||||
{
|
||||
/**
|
||||
* ApplicationController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth:sanctum')
|
||||
->only(['store','update','destroy']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
list($collection, $total) = MediaConductor::request($request);
|
||||
|
||||
return $this->respondAsResource(
|
||||
$collection,
|
||||
['isCollection' => true,
|
||||
'appendData' => ['total' => $total]
|
||||
],
|
||||
function ($options) {
|
||||
return $options['total'] === 0;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @param \App\Models\Media $medium The request media.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(Request $request, Media $medium)
|
||||
{
|
||||
if (MediaConductor::viewable($medium) === true) {
|
||||
return $this->respondAsResource(MediaConductor::model($request, $medium));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new media resource
|
||||
*
|
||||
* @param \App\Http\Requests\MediaRequest $request The uploaded media.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(MediaRequest $request)
|
||||
{
|
||||
// allowed to create a media item
|
||||
if (MediaConductor::creatable() === false) {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
// check for file
|
||||
$file = $request->file('file');
|
||||
if ($file === null) {
|
||||
return $this->respondWithErrors(['file' => 'The browser did not upload the file correctly to the server.']);
|
||||
}
|
||||
|
||||
return $this->storeOrUpdate($request, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the media resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\MediaRequest $request The update request.
|
||||
* @param \App\Models\Media $medium The specified media.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(MediaRequest $request, Media $medium)
|
||||
{
|
||||
// allowed to update a media item
|
||||
if (MediaConductor::updatable($medium) === false) {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
return $this->storeOrUpdate($request, $medium);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new media resource
|
||||
*
|
||||
* @param \App\Http\Requests\MediaRequest $request The uploaded media.
|
||||
* @param \App\Models\Media|null $medium The specified media.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function storeOrUpdate(MediaRequest $request, Media|null $medium)
|
||||
{
|
||||
$file = $request->file('file');
|
||||
if ($file !== null) {
|
||||
// validate file object
|
||||
if ($file->isValid() !== true) {
|
||||
switch ($file->getError()) {
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
return $this->respondTooLarge();
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
return $this->respondWithErrors([$file => 'The file upload was interrupted.']);
|
||||
default:
|
||||
return $this->respondWithErrors(
|
||||
[$file => 'An error occurred uploading the file to the server.']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($file->getSize() > Media::getMaxUploadSize()) {
|
||||
return $this->respondTooLarge();
|
||||
}
|
||||
}
|
||||
|
||||
// create/get media job
|
||||
$mediaJob = null;
|
||||
$data = [];
|
||||
|
||||
if ($request->missing('job_id') === true) {
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
|
||||
$mediaJob = new MediaJob();
|
||||
$mediaJob->user_id = $user->id;
|
||||
if ($medium !== null) {
|
||||
$mediaJob->media_id = $medium->id;
|
||||
}
|
||||
|
||||
if ($request->has('title') === true || $file !== null) {
|
||||
$data['title'] = $request->get('title', '');
|
||||
}
|
||||
|
||||
if ($request->has('name') === true || $file !== null) {
|
||||
$data['name'] = (
|
||||
$request->has('chunk') === true ? $request->get('name', '') : $file->getClientOriginalName()
|
||||
);
|
||||
}
|
||||
|
||||
if ($file !== null) {
|
||||
$data['size'] = $request->has('chunk') === true ? intval($request->get('size', 0)) : $file->getSize();
|
||||
$data['mime_type'] = (
|
||||
$request->has('chunk') === true ? $request->get('mime_type', '') : $file->getMimeType()
|
||||
);
|
||||
}
|
||||
|
||||
if ($request->has('storage') === true || $file !== null) {
|
||||
$data['storage'] = $request->get('storage', '');
|
||||
}
|
||||
|
||||
if ($request->has('security_type') === true || $file !== null) {
|
||||
$data['security']['type'] = $request->get('security_type', '');
|
||||
$data['security']['data'] = $request->get('security_data', '');
|
||||
|
||||
if ($data['security']['type'] === '') {
|
||||
$data['security']['data'] = '';
|
||||
}
|
||||
|
||||
if ($medium === null || strcasecmp($data['security']['type'], $medium->security_type) !== 0) {
|
||||
if ($request->has('storage') === false) {
|
||||
$mime_type = $request->get('mime_type', $medium === null ? '' : $medium->mime_type);
|
||||
$data['storage'] = Media::recommendedStorage($mime_type, $data['security']['type']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
array_key_exists('storage', $data) === true && (
|
||||
array_key_exists('security', $data) === true &&
|
||||
array_key_exists('type', $data['security']) === true
|
||||
) &&
|
||||
array_key_exists('mime_type', $data) === true &&
|
||||
$data['mime_type'] !== ""
|
||||
) {
|
||||
$error = Media::verifyStorage($data['mime_type'], $data['security']['type'], $data['storage']);
|
||||
// Log::error($data['mime_type'] . ' - ' . $data['security']['type'] . ' - ' . $data['storage']);
|
||||
switch ($error) {
|
||||
case Media::STORAGE_VALID:
|
||||
break;
|
||||
case Media::STORAGE_MIME_MISSING:
|
||||
return $this->respondWithErrors(['mime_type' => 'The file type is required.']);
|
||||
case Media::STORAGE_NOT_FOUND:
|
||||
return $this->respondWithErrors(['storage' => 'Storage was not found.']);
|
||||
case Media::STORAGE_INVALID_SECURITY:
|
||||
return $this->respondWithErrors(
|
||||
['storage' => 'Storage invalid for this security requirement.']
|
||||
);
|
||||
default:
|
||||
return $this->respondWithErrors(['storage' => 'Storage verification error occurred.']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->has('transform') === true) {
|
||||
$transform = [];
|
||||
|
||||
foreach (explode(',', $request->get('transform', '')) as $value) {
|
||||
if (is_string($value) === true) {
|
||||
if (preg_match('/^rotate-(-?\d+)$/', $value, $matches) !== false) {
|
||||
$transform['rotate'] = $matches[1];
|
||||
} elseif (preg_match('/^flip-([vh]|vh|hv)$/', $value, $matches) !== false) {
|
||||
$transform['flip'] = $matches[1];
|
||||
} elseif (preg_match('/^crop-(\d+)-(\d+)$/', $value, $matches) !== false) {
|
||||
$transform['crop'] = ['width' => $matches[1], 'height' => $matches[2]];
|
||||
} elseif (preg_match('/^crop-(\d+)-(\d+)-(\d+)-(\d+)$/', $value, $matches) !== false) {
|
||||
$transform['crop'] = [
|
||||
'width' => $matches[1],
|
||||
'height' => $matches[2],
|
||||
'x' => $matches[3],
|
||||
'y' => $matches[4]
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($transform) > 0) {
|
||||
$data['transform'] = $transform;
|
||||
}
|
||||
}//end if
|
||||
|
||||
$mediaJob->setStatusWaiting();
|
||||
} else {
|
||||
$mediaJob = MediaJob::find($request->get('job_id'));
|
||||
if ($mediaJob === null || $mediaJob->exists() === false) {
|
||||
$this->respondNotFound();
|
||||
}
|
||||
|
||||
$data = json_decode($mediaJob->data, true);
|
||||
if ($data === null) {
|
||||
Log::error(`{$mediaJob->id} contains no data`);
|
||||
return $this->respondServerError();
|
||||
}
|
||||
|
||||
if (array_key_exists('name', $data) === false) {
|
||||
Log::error(`{$mediaJob->id} data does not contain the name key`);
|
||||
return $this->respondServerError();
|
||||
}
|
||||
}//end if
|
||||
|
||||
if ($mediaJob === null) {
|
||||
Log::error(`media job does not exist`);
|
||||
return $this->respondServerError();
|
||||
}
|
||||
|
||||
// save uploaded file
|
||||
if ($file !== null) {
|
||||
if ($data['name'] === '') {
|
||||
Log::error(`filename does not exist`);
|
||||
return $this->respondServerError();
|
||||
}
|
||||
|
||||
$temporaryFilePath = generateTempFilePath(
|
||||
pathinfo($data['name'], PATHINFO_EXTENSION),
|
||||
$request->get('chunk', '')
|
||||
);
|
||||
copy($file->path(), $temporaryFilePath);
|
||||
|
||||
if ($request->has('chunk') === true) {
|
||||
$data['chunks'][$request->get('chunk', '1')] = $temporaryFilePath;
|
||||
$data['chunk_count'] = $request->get('chunk_count', 1);
|
||||
} else {
|
||||
$data['file'] = $temporaryFilePath;
|
||||
}
|
||||
}
|
||||
|
||||
$mediaJob->data = json_encode($data, true);
|
||||
$mediaJob->save();
|
||||
$mediaJob->process();
|
||||
|
||||
return $this->respondAsResource(
|
||||
MediaJobConductor::model($request, $mediaJob),
|
||||
['resourceName' => 'media_job', 'respondCode' => HttpResponseCodes::HTTP_ACCEPTED]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\Media $medium Specified media file.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(Media $medium)
|
||||
{
|
||||
if (MediaConductor::destroyable($medium) === true) {
|
||||
$medium->delete();
|
||||
return $this->respondNoContent();
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @param \App\Models\Media $media Specified media.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function download(Request $request, Media $media): Response
|
||||
{
|
||||
$headers = [];
|
||||
|
||||
/* Check file exists */
|
||||
if (Storage::disk($media->storage)->exists($media->name) === false) {
|
||||
return $this->respondNotFound();
|
||||
}
|
||||
|
||||
$updated_at = Carbon::parse(Storage::disk($media->storage)->lastModified($media->name));
|
||||
|
||||
$headerPragma = 'no-cache';
|
||||
$headerCacheControl = 'max-age=0, must-revalidate';
|
||||
$headerExpires = $updated_at->toRfc2822String();
|
||||
|
||||
/* construct user if can */
|
||||
$user = $request->user();
|
||||
if ($user === null && $request->has('token') === true) {
|
||||
$accessToken = PersonalAccessToken::findToken(urldecode($request->input('token')));
|
||||
|
||||
if (
|
||||
$accessToken !== null && (config('sanctum.expiration') === null ||
|
||||
$accessToken->created_at->lte(now()->subMinutes(config('sanctum.expiration'))) === false)
|
||||
) {
|
||||
$user = $accessToken->tokenable;
|
||||
}
|
||||
}
|
||||
|
||||
if ($media->security_type === '') {
|
||||
/* no security */
|
||||
$headerPragma = 'public';
|
||||
$headerExpires = $updated_at->addMonth()->toRfc2822String();
|
||||
} elseif (strcasecmp('password', $media->security_type) === 0) {
|
||||
/* password */
|
||||
if (
|
||||
($user === null || $user->hasPermission('admin/media') === false) &&
|
||||
($request->has('password') === false || $request->get('password') !== $media->security_data)
|
||||
) {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
} elseif (strcasecmp('permission', $media->security_type) === 0) {
|
||||
/* permission */
|
||||
if (
|
||||
$user === null || (
|
||||
$user->hasPermission('admin/media') === false &&
|
||||
$user->hasPermission($media->security_data) === false
|
||||
)
|
||||
) {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}//end if
|
||||
|
||||
// deepcode ignore InsecureHash: Browsers expect Etag to be a md5 hash
|
||||
$headerEtag = md5($updated_at->format('U'));
|
||||
$headerLastModified = $updated_at->toRfc2822String();
|
||||
|
||||
$headers = [
|
||||
'Cache-Control' => $headerCacheControl,
|
||||
'Content-Disposition' => sprintf('inline; filename="%s"', basename($media->name)),
|
||||
'Etag' => $headerEtag,
|
||||
'Expires' => $headerExpires,
|
||||
'Last-Modified' => $headerLastModified,
|
||||
'Pragma' => $headerPragma,
|
||||
];
|
||||
|
||||
$server = request()->server;
|
||||
|
||||
$requestModifiedSince = $server->has('HTTP_IF_MODIFIED_SINCE') &&
|
||||
$server->get('HTTP_IF_MODIFIED_SINCE') === $headerLastModified;
|
||||
|
||||
$requestNoneMatch = $server->has('HTTP_IF_NONE_MATCH') &&
|
||||
$server->get('HTTP_IF_NONE_MATCH') === $headerEtag;
|
||||
|
||||
if ($requestModifiedSince === true || $requestNoneMatch === true) {
|
||||
return response()->make('', 304, $headers);
|
||||
}
|
||||
|
||||
$headers['Content-Type'] = Storage::disk($media->storage)->mimeType($media->name);
|
||||
$headers['Content-Length'] = Storage::disk($media->storage)->size($media->name);
|
||||
$headers['Content-Disposition'] = 'attachment; filename="' . basename($media->name) . '"';
|
||||
|
||||
$stream = Storage::disk($media->storage)->readStream($media->name);
|
||||
return response()->stream(
|
||||
function () use ($stream) {
|
||||
while (ob_get_level() > 0) {
|
||||
ob_end_flush();
|
||||
}
|
||||
fpassthru($stream);
|
||||
},
|
||||
200,
|
||||
$headers
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a File item in a request is valid
|
||||
*
|
||||
* @param UploadedFile $file The file to validate.
|
||||
* @param string $errorKey The error key to use.
|
||||
* @return JsonResponse|null
|
||||
*/
|
||||
private function validateFileObject(UploadedFile $file, string $errorKey = 'file'): JsonResponse|null
|
||||
{
|
||||
if ($file->isValid() !== true) {
|
||||
switch ($file->getError()) {
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
return $this->respondTooLarge();
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
return $this->respondWithErrors([$errorKey => 'The file upload was interrupted.']);
|
||||
default:
|
||||
return $this->respondWithErrors(
|
||||
[$errorKey => 'An error occurred uploading the file to the server.']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($file->getSize() > Media::getMaxUploadSize()) {
|
||||
return $this->respondTooLarge();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user