diff --git a/administrator/components/com_media/resources/scripts/app/Api.es6.js b/administrator/components/com_media/resources/scripts/app/Api.es6.js index cc536ba37388e..40f68bfb82f58 100644 --- a/administrator/components/com_media/resources/scripts/app/Api.es6.js +++ b/administrator/components/com_media/resources/scripts/app/Api.es6.js @@ -181,41 +181,47 @@ class Api { } /** - * Upload a file - * @param name - * @param parent - * @param content base64 encoded string - * @param override boolean whether or not we should override existing files - * @return {Promise.} - */ - upload(name, parent, content, override) { - // Wrap the ajax call into a real promise - return new Promise((resolve, reject) => { - const url = new URL(`${this.baseUrl}&task=api.files&path=${encodeURIComponent(parent)}`); - const data = { - [this.csrfToken]: '1', - name, - content, - }; + * Upload a file, + * In opposite to other API calls the Upload call uses Content-type: application/x-www-form-urlencoded + * + * @param {string} name File name + * @param {string} parent Parent folder path + * @param {File} content File instance + * @param {boolean} override whether we should override existing files or not + * @param {Function} progressCallback Progress callback + * @return {Promise.} + */ + upload(name, parent, content, override, progressCallback) { + const url = `${this.baseUrl}&task=api.files&path=${encodeURIComponent(parent)}`; + const data = new FormData(); + data.append('name', name); + data.append('content', content); - // Append override - if (override === true) { - data.override = true; - } + // Append override + if (override === true) { + data.append('override', 1); + } - Joomla.request({ - url: url.toString(), - method: 'POST', - data: JSON.stringify(data), - headers: { 'Content-Type': 'application/json' }, - onSuccess: (response) => { - notifications.success('COM_MEDIA_UPLOAD_SUCCESS'); - resolve(normalizeItem(JSON.parse(response).data)); - }, - onError: (xhr) => { - reject(xhr); - }, - }); + return Joomla.request({ + url, + method: 'POST', + data, + promise: true, + onBefore: (xhr) => { + if (progressCallback) { + xhr.upload.addEventListener('progress', (event) => { + let progress = 100; + if (event.lengthComputable) { + progress = Math.round((event.loaded / event.total) * 100); + } + progressCallback(progress); + }); + } + }, + }).then((xhr) => { + const response = xhr.responseText; + notifications.success('COM_MEDIA_UPLOAD_SUCCESS'); + return normalizeItem(JSON.parse(response).data); }).catch(handleError); } diff --git a/administrator/components/com_media/resources/scripts/components/browser/browser.vue b/administrator/components/com_media/resources/scripts/components/browser/browser.vue index 1db1e4086bc5e..ddcf99d5b8bc1 100644 --- a/administrator/components/com_media/resources/scripts/components/browser/browser.vue +++ b/administrator/components/com_media/resources/scripts/components/browser/browser.vue @@ -246,24 +246,11 @@ export default { /* Upload files */ upload(file) { - // Create a new file reader instance - const reader = new FileReader(); - - // Add the on load callback - reader.onload = (progressEvent) => { - const { result } = progressEvent.target; - const splitIndex = result.indexOf('base64') + 7; - const content = result.slice(splitIndex, result.length); - - // Upload the file - this.$store.dispatch('uploadFile', { - name: file.name, - parent: this.$store.state.selectedDirectory, - content, - }); - }; - - reader.readAsDataURL(file); + this.$store.dispatch('uploadFile', { + name: file.name, + parent: this.$store.state.selectedDirectory, + content: file, + }); }, // Logic for the dropped file diff --git a/administrator/components/com_media/resources/scripts/components/toolbar/toolbar.vue b/administrator/components/com_media/resources/scripts/components/toolbar/toolbar.vue index 963d8f67081f9..a95adb8ffacec 100644 --- a/administrator/components/com_media/resources/scripts/components/toolbar/toolbar.vue +++ b/administrator/components/com_media/resources/scripts/components/toolbar/toolbar.vue @@ -8,6 +8,15 @@ v-if="isLoading" class="media-loader" /> +
+
+
0; + }, + uploadProgress() { + // Use extra 2 for initial visibility + return Math.max(this.$store.state.uploadProgress, 2); + }, atLeastOneItemSelected() { return this.$store.state.selectedItems.length > 0; }, diff --git a/administrator/components/com_media/resources/scripts/components/upload/upload.vue b/administrator/components/com_media/resources/scripts/components/upload/upload.vue index 012b82a1b6367..a2c9fa737bed6 100644 --- a/administrator/components/com_media/resources/scripts/components/upload/upload.vue +++ b/administrator/components/com_media/resources/scripts/components/upload/upload.vue @@ -47,24 +47,11 @@ export default { // Loop through array of files and upload each file Array.from(files).forEach((file) => { - // Create a new file reader instance - const reader = new FileReader(); - - // Add the on load callback - reader.onload = (progressEvent) => { - const { result } = progressEvent.target; - const splitIndex = result.indexOf('base64') + 7; - const content = result.slice(splitIndex, result.length); - - // Upload the file - this.$store.dispatch('uploadFile', { - name: file.name, - parent: this.$store.state.selectedDirectory, - content, - }); - }; - - reader.readAsDataURL(file); + this.$store.dispatch('uploadFile', { + name: file.name, + parent: this.$store.state.selectedDirectory, + content: file, + }); }); }, }, diff --git a/administrator/components/com_media/resources/scripts/store/actions.es6.js b/administrator/components/com_media/resources/scripts/store/actions.es6.js index 804ce5e13d617..3927d27daf266 100644 --- a/administrator/components/com_media/resources/scripts/store/actions.es6.js +++ b/administrator/components/com_media/resources/scripts/store/actions.es6.js @@ -125,22 +125,29 @@ export const createDirectory = (context, payload) => { }; /** - * Create a new folder + * Upload a file * @param context - * @param payload object with the new folder name and its parent directory + * @param payload object with the new file data */ export const uploadFile = (context, payload) => { if (!api.canCreate) { return; } - context.commit(types.SET_IS_LOADING, true); - api.upload(payload.name, payload.parent, payload.content, payload.override || false) + + // Commit the progress + context.commit(types.UPDATE_ACTIVE_UPLOADS, { name: payload.name, progress: 0 }); + const uploadProgress = (progress) => { + context.commit(types.UPDATE_ACTIVE_UPLOADS, { name: payload.name, progress }); + }; + + // Do file upload + api.upload(payload.name, payload.parent, payload.content, payload.override || false, uploadProgress) .then((file) => { context.commit(types.UPLOAD_SUCCESS, file); - context.commit(types.SET_IS_LOADING, false); + context.commit(types.UPDATE_ACTIVE_UPLOADS, { name: payload.name, completed: true }); }) .catch((error) => { - context.commit(types.SET_IS_LOADING, false); + context.commit(types.UPDATE_ACTIVE_UPLOADS, { name: payload.name, completed: true }); // Handle file exists if (error.status === 409) { diff --git a/administrator/components/com_media/resources/scripts/store/mutation-types.es6.js b/administrator/components/com_media/resources/scripts/store/mutation-types.es6.js index 622658040e77f..040008014c8de 100644 --- a/administrator/components/com_media/resources/scripts/store/mutation-types.es6.js +++ b/administrator/components/com_media/resources/scripts/store/mutation-types.es6.js @@ -60,3 +60,6 @@ export const UPDATE_SORT_BY = 'UPDATE_SORT_BY'; // Update sorting direction export const UPDATE_SORT_DIRECTION = 'UPDATE_SORT_DIRECTION'; + +// Update list of files currently uploading and their progress +export const UPDATE_ACTIVE_UPLOADS = 'UPDATE_ACTIVE_UPLOADS'; diff --git a/administrator/components/com_media/resources/scripts/store/mutations.es6.js b/administrator/components/com_media/resources/scripts/store/mutations.es6.js index e81deedf1d753..6b46ca7ea84b9 100644 --- a/administrator/components/com_media/resources/scripts/store/mutations.es6.js +++ b/administrator/components/com_media/resources/scripts/store/mutations.es6.js @@ -493,4 +493,43 @@ export default { [types.UPDATE_SORT_DIRECTION]: (state, payload) => { state.sortDirection = payload === 'asc' ? 'asc' : 'desc'; }, + + /** + * Update list of active uploads + * @param state + * @param payload As following {name: fileName, progress: 0} or {name: fileName, completed: true} + */ + [types.UPDATE_ACTIVE_UPLOADS]: (state, payload) => { + let isNew = true; + let progress = 0; + let toRemove = -1; + + // Collect progress for active uploads + state.activeUploads.forEach((item, idx) => { + if (item.name === payload.name) { + isNew = false; + item.progress = Math.max(item.progress, Math.min(100, payload.progress || 0)); + + if (payload.completed) { + toRemove = idx; + } + } + // Pick element with the smallest progress + if (item.progress > 0) { + progress = !progress ? item.progress : Math.min(progress, item.progress); + } + }); + + // Add new item to the list + if (isNew) { + state.activeUploads.push({ name: payload.name, progress: payload.progress }); + } + + // Remove completed item from the list + if (toRemove !== -1) { + state.activeUploads.splice(toRemove, 1); + } + + state.uploadProgress = progress; + }, }; diff --git a/administrator/components/com_media/resources/scripts/store/state.es6.js b/administrator/components/com_media/resources/scripts/store/state.es6.js index 0b38946fe0701..04b13925becfe 100644 --- a/administrator/components/com_media/resources/scripts/store/state.es6.js +++ b/administrator/components/com_media/resources/scripts/store/state.es6.js @@ -119,4 +119,8 @@ export default { sortBy: storedState && storedState.sortBy ? storedState.sortBy : 'name', // The sorting direction sortDirection: storedState && storedState.sortDirection ? storedState.sortDirection : 'asc', + // Active uploads, [{name: fileName, progress: 0}] + activeUploads: [], + // Upload progress, from 0 to 100 + uploadProgress: 0, }; diff --git a/administrator/components/com_media/src/Controller/ApiController.php b/administrator/components/com_media/src/Controller/ApiController.php index 570c5448fa0dc..758791b77cba5 100644 --- a/administrator/components/com_media/src/Controller/ApiController.php +++ b/administrator/components/com_media/src/Controller/ApiController.php @@ -21,6 +21,9 @@ use Joomla\Component\Media\Administrator\Exception\FileExistsException; use Joomla\Component\Media\Administrator\Exception\FileNotFoundException; use Joomla\Component\Media\Administrator\Exception\InvalidPathException; +use Joomla\Component\Media\Administrator\File\TmpFileUpload; +use Joomla\Filesystem\File; +use Joomla\Filesystem\Path; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -190,15 +193,55 @@ public function postFiles() throw new \Exception(Text::_('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED'), 403); } - $adapter = $this->getAdapter(); - $path = $this->getPath(); - $content = $this->input->json; - $name = $content->getString('name'); - $mediaContent = base64_decode($content->get('content', '', 'raw')); - $override = $content->get('override', false); + $adapter = $this->getAdapter(); + $path = $this->getPath(); + $tmpFile = ''; + + // Get the data depending on the request type + if ($this->input->json->count()) { + $content = $this->input->json; + $name = $content->getString('name'); + $mediaContent = base64_decode($content->get('content', '', 'raw')); + $mediaLength = 0; + + // Create tmp file + if ($mediaContent) { + $tmpContent = $mediaContent; + $tmpFile = Path::clean($this->app->get('tmp_path') . '/tmp_upload/' . uniqid('tmp-', true)); + $mediaLength = \strlen($tmpContent); + $mediaContent = new TmpFileUpload([ + 'name' => $name, + 'tmp_name' => $tmpFile, + 'size' => $mediaLength, + 'error' => 0, + ]); + + if (!File::write($tmpFile, $tmpContent)) { + throw new \Exception(Text::_('JLIB_MEDIA_ERROR_UPLOAD_INPUT')); + } + } + } else { + $content = $this->input->post; + $name = $content->getString('name'); + $mediaContent = null; + $mediaLength = 0; + $file = $this->input->files->get('content', []); + + if ($file) { + $file['name'] = $name; + $mediaContent = new TmpFileUpload($file); + $mediaLength = $mediaContent->getSize(); + + if ($mediaContent->getError()) { + throw new \Exception(Text::_('JLIB_MEDIA_ERROR_UPLOAD_INPUT')); + } + } + } + + $override = $content->getBool('override', false); if ($mediaContent) { - $this->checkFileSize(\strlen($mediaContent)); + $this->checkFileSize($mediaLength); // A file needs to be created $name = $this->getModel()->createFile($adapter, $name, $path, $mediaContent, $override); @@ -210,6 +253,13 @@ public function postFiles() $options = []; $options['url'] = $this->input->getBool('url', false); + if ($tmpFile) { + try { + File::delete($tmpFile); + } catch (\Exception $e) { + } + } + return $this->getModel()->getFile($adapter, $path . '/' . $name, $options); } @@ -262,15 +312,53 @@ public function putFiles() $adapter = $this->getAdapter(); $path = $this->getPath(); + $tmpFile = ''; + + // Get the data depending on the request type + if ($this->input->json->count()) { + $content = $this->input->json; + $name = $content->getString('name'); + $mediaContent = base64_decode($content->get('content', '', 'raw')); + $mediaLength = 0; + + // Create tmp file + if ($mediaContent) { + $tmpContent = $mediaContent; + $tmpFile = Path::clean($this->app->get('tmp_path') . '/tmp_upload/' . uniqid('tmp-', true)); + $mediaLength = \strlen($tmpContent); + $mediaContent = new TmpFileUpload([ + 'name' => $name, + 'tmp_name' => $tmpFile, + 'size' => $mediaLength, + 'error' => 0, + ]); + + if (!File::write($tmpFile, $tmpContent)) { + throw new \Exception(Text::_('JLIB_MEDIA_ERROR_UPLOAD_INPUT')); + } + } + } else { + $content = $this->input->post; + $mediaContent = null; + $mediaLength = 0; + $file = $this->input->files->get('content', []); + + if ($file) { + $mediaContent = new TmpFileUpload($file); + $mediaLength = $mediaContent->getSize(); + + if ($mediaContent->getError()) { + throw new \Exception(Text::_('JLIB_MEDIA_ERROR_UPLOAD_INPUT')); + } + } + } - $content = $this->input->json; $name = basename($path); - $mediaContent = base64_decode($content->get('content', '', 'raw')); $newPath = $content->getString('newPath', null); $move = $content->get('move', true); - if ($mediaContent != null) { - $this->checkFileSize(\strlen($mediaContent)); + if ($mediaContent) { + $this->checkFileSize($mediaLength); $this->getModel()->updateFile($adapter, $name, str_replace($name, '', $path), $mediaContent); } @@ -287,6 +375,13 @@ public function putFiles() $path = $destinationPath; } + if ($tmpFile) { + try { + File::delete($tmpFile); + } catch (\Exception $e) { + } + } + return $this->getModel()->getFile($adapter, $path); } diff --git a/administrator/components/com_media/src/File/TmpFileUpload.php b/administrator/components/com_media/src/File/TmpFileUpload.php new file mode 100644 index 0000000000000..ced6ca861b453 --- /dev/null +++ b/administrator/components/com_media/src/File/TmpFileUpload.php @@ -0,0 +1,143 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Media\Administrator\File; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Class wrapper for uploaded file $_FILE. + * + * @since __DEPLOY_VERSION__ + */ +final class TmpFileUpload +{ + /** + * The file name + * + * @var string + * + * @since __DEPLOY_VERSION__ + */ + protected string $name = ''; + + /** + * The file path + * + * @var string + * + * @since __DEPLOY_VERSION__ + */ + protected string $uri = ''; + + /** + * The file size + * + * @var integer + * + * @since __DEPLOY_VERSION__ + */ + protected int $size = 0; + + /** + * The upload error code, if any. + * See https://www.php.net/manual/en/filesystem.constants.php#constant.upload-err-cant-write + * + * @var integer + * + * @since __DEPLOY_VERSION__ + */ + protected int $error = 0; + + /** + * Class constructor. + * + * @param array $file A single $_FILE instance with: name, tmp_name, size, error. + * + * @since __DEPLOY_VERSION__ + */ + public function __construct(array $file) + { + $this->name = $file['name'] ?? ''; + $this->uri = $file['tmp_name'] ?? ''; + $this->size = $file['size'] ?? 0; + $this->error = $file['error'] ?? 0; + } + + /** + * Reading the file data while accessing to object as string. + * Made for backward compatibility only. + * + * @return string + * + * @since __DEPLOY_VERSION__ + * + * @deprecated __DEPLOY_VERSION__ will be removed in 7.0 without replacement. + */ + final public function __toString(): string + { + @trigger_error( + 'Stringification and direct accessing to the file content are deprecated, and will be removed in 7.0.', + E_USER_DEPRECATED + ); + + return file_get_contents($this->getUri()) ?: ''; + } + + /** + * Return the name. + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + public function getName(): string + { + return $this->name; + } + + /** + * Return the path to the file. + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + public function getUri(): string + { + return $this->uri; + } + + /** + * Return file size. + * + * @return integer + * + * @since __DEPLOY_VERSION__ + */ + public function getSize(): int + { + return $this->size; + } + + /** + * Return upload error code. + * + * @return integer + * + * @since __DEPLOY_VERSION__ + */ + public function getError(): int + { + return $this->error; + } +} diff --git a/api/components/com_media/src/Controller/MediaController.php b/api/components/com_media/src/Controller/MediaController.php index 759a6bb244935..1bfe6ab64335d 100644 --- a/api/components/com_media/src/Controller/MediaController.php +++ b/api/components/com_media/src/Controller/MediaController.php @@ -18,8 +18,11 @@ use Joomla\CMS\MVC\Controller\ApiController; use Joomla\Component\Media\Administrator\Exception\FileExistsException; use Joomla\Component\Media\Administrator\Exception\InvalidPathException; +use Joomla\Component\Media\Administrator\File\TmpFileUpload; use Joomla\Component\Media\Administrator\Provider\ProviderManagerHelperTrait; use Joomla\Component\Media\Api\Model\MediumModel; +use Joomla\Filesystem\File; +use Joomla\Filesystem\Path; use Tobscure\JsonApi\Exception\InvalidParameterException; // phpcs:disable PSR1.Files.SideEffects @@ -320,41 +323,62 @@ protected function save($recordKey = null) /** @var MediumModel $model */ $model = $this->getModel($modelName, '', ['ignore_request' => true, 'state' => $this->modelState]); - $json = $this->input->json; - - // Decode content, if any - if ($content = base64_decode($json->get('content', '', 'raw'))) { - $this->checkContent(); + $json = $this->input->json; + $name = basename($json->getString('path', '')); + $mediaContent = base64_decode($json->get('content', '', 'raw')); + $tmpFile = ''; + + // Create tmp file + if ($mediaContent) { + $tmpContent = $mediaContent; + $tmpFile = Path::clean($this->app->get('tmp_path') . '/tmp_upload/' . uniqid('tmp-', true)); + $mediaLength = \strlen($tmpContent); + $mediaContent = new TmpFileUpload([ + 'name' => $name, + 'tmp_name' => $tmpFile, + 'size' => $mediaLength, + 'error' => 0, + ]); + + $this->checkContent($mediaLength); + + if (!File::write($tmpFile, $tmpContent)) { + throw new \Exception(Text::_('JLIB_MEDIA_ERROR_UPLOAD_INPUT')); + } } // If there is no content, com_media assumes the path refers to a folder. - $this->modelState->set('content', $content); + $this->modelState->set('content', $mediaContent); + + $result = $model->save(); + + if ($tmpFile) { + try { + File::delete($tmpFile); + } catch (\Exception) { + } + } - return $model->save(); + return $result; } /** * Performs various checks to see if it is allowed to save the content. * + * @param integer $fileSize The size of submitted file + * * @return void * * @since 4.1.0 * * @throws \RuntimeException */ - private function checkContent(): void + private function checkContent(int $fileSize): void { - $params = ComponentHelper::getParams('com_media'); - $helper = new \Joomla\CMS\Helper\MediaHelper(); - $serverlength = $this->input->server->getInt('CONTENT_LENGTH'); - - // Check if the size of the request body does not exceed various server imposed limits. - if ( - ($params->get('upload_maxsize', 0) > 0 && $serverlength > ($params->get('upload_maxsize', 0) * 1024 * 1024)) - || $serverlength > $helper->toBytes(\ini_get('upload_max_filesize')) - || $serverlength > $helper->toBytes(\ini_get('post_max_size')) - || $serverlength > $helper->toBytes(\ini_get('memory_limit')) - ) { + $params = ComponentHelper::getParams('com_media'); + $paramsUploadMaxsize = $params->get('upload_maxsize', 0) * 1024 * 1024; + + if ($paramsUploadMaxsize > 0 && $fileSize > $paramsUploadMaxsize) { throw new \RuntimeException(Text::_('COM_MEDIA_ERROR_WARNFILETOOLARGE'), 400); } } diff --git a/build/media_source/com_media/scss/components/_media-toolbar.scss b/build/media_source/com_media/scss/components/_media-toolbar.scss index 14e070f8dbedb..44d7ae13727cb 100644 --- a/build/media_source/com_media/scss/components/_media-toolbar.scss +++ b/build/media_source/com_media/scss/components/_media-toolbar.scss @@ -80,3 +80,17 @@ right: 0; } } + +.media-upload-progress { + position: absolute; + top: 100%; + right: 0; + left: 0; + + .progress-bar { + width: 0; + height: 5px; + background-image: $toolbar-loader-color; + transition: width .3s ease; + } +} diff --git a/plugins/filesystem/local/src/Adapter/LocalAdapter.php b/plugins/filesystem/local/src/Adapter/LocalAdapter.php index 9f8ecd9a2d9eb..2b495b9f81688 100644 --- a/plugins/filesystem/local/src/Adapter/LocalAdapter.php +++ b/plugins/filesystem/local/src/Adapter/LocalAdapter.php @@ -22,6 +22,7 @@ use Joomla\Component\Media\Administrator\Adapter\AdapterInterface; use Joomla\Component\Media\Administrator\Exception\FileNotFoundException; use Joomla\Component\Media\Administrator\Exception\InvalidPathException; +use Joomla\Component\Media\Administrator\File\TmpFileUpload; use Joomla\Filesystem\Exception\FilesystemException; use Joomla\Filesystem\File; use Joomla\Filesystem\Folder; @@ -259,6 +260,11 @@ public function createFile(string $name, string $path, $data): string $this->checkContent($localPath, $data); + // Create a stream reference to the file, because we cannot use File::upload() for now. + if ($data instanceof TmpFileUpload) { + $data = fopen($data->getUri(), 'r'); + } + try { File::write($localPath, $data); } catch (FilesystemException) { @@ -300,6 +306,11 @@ public function updateFile(string $name, string $path, $data) $this->checkContent($localPath, $data); + // Create a stream reference to the file, because we cannot use File::upload() for now. + if ($data instanceof TmpFileUpload) { + $data = fopen($data->getUri(), 'r'); + } + try { File::write($localPath, $data); } catch (FilesystemException) { @@ -832,35 +843,52 @@ private function getSafeName(string $name): string /** * Performs various check if it is allowed to save the content with the given name. * - * @param string $localPath The local path - * @param string $mediaContent The media content + * @param string $localPath The local path + * @param string|TmpFileUpload $mediaContent The media content as TmpFileUpload or string * * @return void * * @since 4.0.0 * @throws \Exception */ - private function checkContent(string $localPath, string $mediaContent) + private function checkContent(string $localPath, string|TmpFileUpload $mediaContent) { $name = $this->getFileName($localPath); // The helper $helper = new MediaHelper(); + // Reconstruct the file array, expected by MediaHelper::canUpload() + $file = [ + 'name' => $name, + 'size' => 0, + 'tmp_name' => '', + ]; + $tmpFile = ''; + + if ($mediaContent instanceof TmpFileUpload) { + $file['size'] = $mediaContent->getSize(); + $file['tmp_name'] = $mediaContent->getUri(); + } else { + // @todo the $mediaContent data should always be TmpFileUpload + $tmpFile = Path::clean(\dirname($localPath) . '/' . uniqid() . '.' . strtolower(File::getExt($name))); - // @todo find a better way to check the input, by not writing the file to the disk - $tmpFile = Path::clean(\dirname($localPath) . '/' . uniqid() . '.' . strtolower(File::getExt($name))); + try { + File::write($tmpFile, $mediaContent); + } catch (FilesystemException $exception) { + throw new \Exception(Text::_('JLIB_MEDIA_ERROR_UPLOAD_INPUT'), 500, $exception); + } - try { - File::write($tmpFile, $mediaContent); - } catch (FilesystemException $exception) { - throw new \Exception(Text::_('JLIB_MEDIA_ERROR_UPLOAD_INPUT'), 500); + $file['size'] = \strlen($mediaContent); + $file['tmp_name'] = $tmpFile; } - $can = $helper->canUpload(['name' => $name, 'size' => \strlen($mediaContent), 'tmp_name' => $tmpFile], 'com_media'); + $can = $helper->canUpload($file, 'com_media'); - try { - File::delete($tmpFile); - } catch (FilesystemException) { + if ($tmpFile) { + try { + File::delete($tmpFile); + } catch (FilesystemException) { + } } if (!$can) {