diff --git a/src/12a2a8de-a893-4dbb-a53d-b52de4f6cb0e/code.php b/src/12a2a8de-a893-4dbb-a53d-b52de4f6cb0e/code.php index 4cf69c4..5eb289c 100644 --- a/src/12a2a8de-a893-4dbb-a53d-b52de4f6cb0e/code.php +++ b/src/12a2a8de-a893-4dbb-a53d-b52de4f6cb0e/code.php @@ -91,12 +91,19 @@ final class Type if (($fileType = $this->details($guid)) !== null && $this->validTarget($fileType, $target)) { + // some safety checks + $path = isset($fileType->path) && is_string($fileType->path) && trim($fileType->path) !== '' ? trim($fileType->path) : null; + $path = ($path !== null && is_dir($path) && is_writable($path)) ? $path : null; + return [ + 'name' => $fileType->name ?? 'files', + 'access' => $fileType->access ?? 1, + 'download_access' => $fileType->download_access ?? 1, 'field' => $this->getFieldName($fileType), 'type' => $this->getFieldName($fileType), 'formats' => $this->getAllowFormats($fileType) ?? [], 'filter' => $fileType->filter ?? null, - 'path' => $fileType->path ?? null + 'path' => $path ]; } diff --git a/src/12a2a8de-a893-4dbb-a53d-b52de4f6cb0e/code.power b/src/12a2a8de-a893-4dbb-a53d-b52de4f6cb0e/code.power index 68a8b18..d800e2c 100644 --- a/src/12a2a8de-a893-4dbb-a53d-b52de4f6cb0e/code.power +++ b/src/12a2a8de-a893-4dbb-a53d-b52de4f6cb0e/code.power @@ -67,12 +67,19 @@ if (($fileType = $this->details($guid)) !== null && $this->validTarget($fileType, $target)) { + // some safety checks + $path = isset($fileType->path) && is_string($fileType->path) && trim($fileType->path) !== '' ? trim($fileType->path) : null; + $path = ($path !== null && is_dir($path) && is_writable($path)) ? $path : null; + return [ + 'name' => $fileType->name ?? 'files', + 'access' => $fileType->access ?? 1, + 'download_access' => $fileType->download_access ?? 1, 'field' => $this->getFieldName($fileType), 'type' => $this->getFieldName($fileType), 'formats' => $this->getAllowFormats($fileType) ?? [], 'filter' => $fileType->filter ?? null, - 'path' => $fileType->path ?? null + 'path' => $path ]; } diff --git a/src/46b98346-ec98-42b3-a393-96c7d1282b1c/code.php b/src/46b98346-ec98-42b3-a393-96c7d1282b1c/code.php index 995c1c8..841a3a7 100644 --- a/src/46b98346-ec98-42b3-a393-96c7d1282b1c/code.php +++ b/src/46b98346-ec98-42b3-a393-96c7d1282b1c/code.php @@ -522,19 +522,35 @@ final class UsersSubform implements GuidInterface, SubformInterface */ private function assignUserGroups(array &$details, ?User $user, array $item): void { - $details['groups'] = $user !== null ? $user->groups : []; + $groups = $user !== null ? (array) $user->groups : []; if (!empty($item['entity_type'])) { - $groups = Component::getParams()->get($item['entity_type'] . '_groups', []); - foreach ($groups as $group) + $global_entity_groups = Component::getParams()->get($item['entity_type'] . '_groups', []); + foreach ($global_entity_groups as $group) { - if (!in_array($group, $details['groups'])) + if (!in_array($group, $groups)) { - $details['groups'][] = $group; + $groups[] = $group; } } } + + // Ensure $details['groups'] is an array if it exists, else default to an empty array + $detailsGroups = isset($details['groups']) ? (array) $details['groups'] : []; + + // Merge the arrays and remove duplicates + $mergedGroups = array_unique(array_merge($detailsGroups, $groups)); + + // Only set $details['groups'] if the merged array is not empty + if (!empty($mergedGroups)) + { + $details['groups'] = $mergedGroups; + } + else + { + unset($details['groups']); + } } /** diff --git a/src/46b98346-ec98-42b3-a393-96c7d1282b1c/code.power b/src/46b98346-ec98-42b3-a393-96c7d1282b1c/code.power index 7524d48..feca9f9 100644 --- a/src/46b98346-ec98-42b3-a393-96c7d1282b1c/code.power +++ b/src/46b98346-ec98-42b3-a393-96c7d1282b1c/code.power @@ -490,19 +490,35 @@ */ private function assignUserGroups(array &$details, ?User $user, array $item): void { - $details['groups'] = $user !== null ? $user->groups : []; + $groups = $user !== null ? (array) $user->groups : []; if (!empty($item['entity_type'])) { - $groups = Component::getParams()->get($item['entity_type'] . '_groups', []); - foreach ($groups as $group) + $global_entity_groups = Component::getParams()->get($item['entity_type'] . '_groups', []); + foreach ($global_entity_groups as $group) { - if (!in_array($group, $details['groups'])) + if (!in_array($group, $groups)) { - $details['groups'][] = $group; + $groups[] = $group; } } } + + // Ensure $details['groups'] is an array if it exists, else default to an empty array + $detailsGroups = isset($details['groups']) ? (array) $details['groups'] : []; + + // Merge the arrays and remove duplicates + $mergedGroups = array_unique(array_merge($detailsGroups, $groups)); + + // Only set $details['groups'] if the merged array is not empty + if (!empty($mergedGroups)) + { + $details['groups'] = $mergedGroups; + } + else + { + unset($details['groups']); + } } /** diff --git a/src/67a5e0ca-0ff0-4979-9b41-da0b09988016/README.md b/src/67a5e0ca-0ff0-4979-9b41-da0b09988016/README.md index 7d9fda7..0023d97 100644 --- a/src/67a5e0ca-0ff0-4979-9b41-da0b09988016/README.md +++ b/src/67a5e0ca-0ff0-4979-9b41-da0b09988016/README.md @@ -16,10 +16,12 @@ class Manager << (F,LightGreen) >> #RoyalBlue { # Items $items # Type $type # Handler $handler + # User $user # string $table + __construct(Item $item, Items $items, ...) + upload(string $guid, string $entity, ...) : void - + delete(string $guid, string $entity, ...) : void + + download(string $guid) : ?array + + delete(string $guid) : void + table(string $table) : self + getTable() : string # modelFileDetails(array $details, string $guid, ...) : object @@ -49,16 +51,18 @@ note right of Manager::upload string $target end note +note right of Manager::download + Get the file details for download + + since: 5.0.2 + return: ?array +end note + note right of Manager::delete - Delete a file of a given entity. + Delete a file. since: 5.0.2 return: void - - arguments: - string $guid - string $entity - string $target end note note right of Manager::table @@ -86,6 +90,7 @@ note right of Manager::modelFileDetails string $guid string $entity string $target + array $fileType end note @enduml diff --git a/src/67a5e0ca-0ff0-4979-9b41-da0b09988016/code.php b/src/67a5e0ca-0ff0-4979-9b41-da0b09988016/code.php index 1a74e4d..b2db788 100644 --- a/src/67a5e0ca-0ff0-4979-9b41-da0b09988016/code.php +++ b/src/67a5e0ca-0ff0-4979-9b41-da0b09988016/code.php @@ -12,12 +12,16 @@ namespace VDM\Joomla\Componentbuilder\File; -use Joomla\CMS\Language\Text; +use Joomla\CMS\Factory; +use Joomla\CMS\User\User; +use Joomla\CMS\Language\Text; +use Joomla\Filesystem\File; use VDM\Joomla\Interfaces\Data\ItemInterface as Item; use VDM\Joomla\Interfaces\Data\ItemsInterface as Items; use VDM\Joomla\Data\Guid; use VDM\Joomla\Componentbuilder\File\Type; use VDM\Joomla\Componentbuilder\File\Handler; +use VDM\Joomla\Utilities\MimeHelper; /** @@ -66,6 +70,14 @@ final class Manager */ protected Handler $handler; + /** + * The active user + * + * @var User + * @since 5.0.2 + */ + protected User $user; + /** * Table Name * @@ -90,6 +102,7 @@ final class Manager $this->items = $items; $this->type = $type; $this->handler = $handler; + $this->user = Factory::getApplication()->getIdentity(); } /** @@ -111,6 +124,12 @@ final class Manager throw new \InvalidArgumentException(Text::sprintf('COM_COMPONENTBUILDER_FILE_TYPE_NOT_VALID_IN_S_AREA', $target)); } + // make sure the user have permissions to upload this file type + if (!in_array($fileType['access'], $this->user->getAuthorisedViewLevels())) + { + throw new \InvalidArgumentException(Text::sprintf('COM_COMPONENTBUILDER_YOU_DO_NOT_HAVE_PERMISSIONS_TO_UPLOAD_S', $fileType['name'])); + } + $details = $this->handler ->setEnqueueError(false) ->setLegalFormats($fileType['formats']) @@ -127,25 +146,57 @@ final class Manager throw new \RuntimeException($this->handler->getErrors()); } + // we might need to crop images + if ($fileType['type'] === 'image') + { + // $this->cropImage($details, $guid); + } + // store file in the file table $this->item->table($this->getTable())->set( - $this->modelFileDetails($details, $guid, $entity, $target) + $this->modelFileDetails($details, $guid, $entity, $target, $fileType) ); } /** - * Delete a file of a given entity. + * Get the file details for download * - * @param string $guid The file guid - * @param string $entity The entity guid - * @param string $target The target entity name + * @param string $guid The file guid + * + * @return array|null + * @since 5.0.2 + */ + public function download(string $guid): ?array + { + if (($file = $this->item->table($this->getTable())->get($guid)) !== null && + in_array($file->access, $this->user->getAuthorisedViewLevels())) + { + return (array) $file; + } + + return null; + } + + /** + * Delete a file. + * + * @param string $guid The file guid * * @return void * @since 5.0.2 */ - public function delete(string $guid, string $entity, string $target): void + public function delete(string $guid): void { - + if (($file = $this->item->table($this->getTable())->get($guid)) !== null && + in_array($file->access, $this->user->getAuthorisedViewLevels())) + { + $this->item->table($this->getTable())->delete($guid); // from DB + + if (is_file($file->file_path) && is_writable($file->file_path)) + { + File::delete($file->file_path); // from file system + } + } } /** @@ -177,24 +228,27 @@ final class Manager /** * model the file details to store in the file table * - * @param array $details The uploaded file details. - * @param string $guid The file type guid - * @param string $entity The entity guid - * @param string $target The target entity name + * @param array $details The uploaded file details. + * @param string $guid The file type guid + * @param string $entity The entity guid + * @param string $target The target entity name + * @param array $fileType The file type * * @return object * @since 5.0.2 */ - protected function modelFileDetails(array $details, string $guid, string $entity, string $target): object + protected function modelFileDetails(array $details, string $guid, string $entity, string $target, array $fileType): object { return (object) [ 'name' => $details['name'], 'file_type' => $guid, - 'ext' => $details['extension'] ?? 'error', - 'size_kb' => $details['size'] ?? 0, - 'filepath' => $details['full_path'], + 'extension' => $details['extension'] ?? 'error', + 'size' => $details['size'] ?? 0, + 'mime' => $details['mime'] ?? '', + 'file_path' => $details['full_path'], 'entity_type' => $target, 'entity' => $entity, + 'access' => $fileType['download_access'] ?? 1, 'guid' => $this->getGuid('guid'), ]; } diff --git a/src/67a5e0ca-0ff0-4979-9b41-da0b09988016/code.power b/src/67a5e0ca-0ff0-4979-9b41-da0b09988016/code.power index 270fa86..8ab1226 100644 --- a/src/67a5e0ca-0ff0-4979-9b41-da0b09988016/code.power +++ b/src/67a5e0ca-0ff0-4979-9b41-da0b09988016/code.power @@ -37,6 +37,14 @@ */ protected Handler $handler; + /** + * The active user + * + * @var User + * @since 5.0.2 + */ + protected User $user; + /** * Table Name * @@ -61,6 +69,7 @@ $this->items = $items; $this->type = $type; $this->handler = $handler; + $this->user = Factory::getApplication()->getIdentity(); } /** @@ -82,6 +91,12 @@ throw new \InvalidArgumentException(Text::sprintf('File type not valid in %s area.', $target)); } + // make sure the user have permissions to upload this file type + if (!in_array($fileType['access'], $this->user->getAuthorisedViewLevels())) + { + throw new \InvalidArgumentException(Text::sprintf('You do not have permissions to upload (%s).', $fileType['name'])); + } + $details = $this->handler ->setEnqueueError(false) ->setLegalFormats($fileType['formats']) @@ -98,25 +113,57 @@ throw new \RuntimeException($this->handler->getErrors()); } + // we might need to crop images + if ($fileType['type'] === 'image') + { + // $this->cropImage($details, $guid); + } + // store file in the file table $this->item->table($this->getTable())->set( - $this->modelFileDetails($details, $guid, $entity, $target) + $this->modelFileDetails($details, $guid, $entity, $target, $fileType) ); } /** - * Delete a file of a given entity. + * Get the file details for download * - * @param string $guid The file guid - * @param string $entity The entity guid - * @param string $target The target entity name + * @param string $guid The file guid + * + * @return array|null + * @since 5.0.2 + */ + public function download(string $guid): ?array + { + if (($file = $this->item->table($this->getTable())->get($guid)) !== null && + in_array($file->access, $this->user->getAuthorisedViewLevels())) + { + return (array) $file; + } + + return null; + } + + /** + * Delete a file. + * + * @param string $guid The file guid * * @return void * @since 5.0.2 */ - public function delete(string $guid, string $entity, string $target): void + public function delete(string $guid): void { - + if (($file = $this->item->table($this->getTable())->get($guid)) !== null && + in_array($file->access, $this->user->getAuthorisedViewLevels())) + { + $this->item->table($this->getTable())->delete($guid); // from DB + + if (is_file($file->file_path) && is_writable($file->file_path)) + { + File::delete($file->file_path); // from file system + } + } } /** @@ -148,24 +195,27 @@ /** * model the file details to store in the file table * - * @param array $details The uploaded file details. - * @param string $guid The file type guid - * @param string $entity The entity guid - * @param string $target The target entity name + * @param array $details The uploaded file details. + * @param string $guid The file type guid + * @param string $entity The entity guid + * @param string $target The target entity name + * @param array $fileType The file type * * @return object * @since 5.0.2 */ - protected function modelFileDetails(array $details, string $guid, string $entity, string $target): object + protected function modelFileDetails(array $details, string $guid, string $entity, string $target, array $fileType): object { return (object) [ 'name' => $details['name'], 'file_type' => $guid, - 'ext' => $details['extension'] ?? 'error', - 'size_kb' => $details['size'] ?? 0, - 'filepath' => $details['full_path'], + 'extension' => $details['extension'] ?? 'error', + 'size' => $details['size'] ?? 0, + 'mime' => $details['mime'] ?? '', + 'file_path' => $details['full_path'], 'entity_type' => $target, 'entity' => $entity, + 'access' => $fileType['download_access'] ?? 1, 'guid' => $this->getGuid('guid'), ]; } \ No newline at end of file diff --git a/src/67a5e0ca-0ff0-4979-9b41-da0b09988016/settings.json b/src/67a5e0ca-0ff0-4979-9b41-da0b09988016/settings.json index 95d452e..bddf268 100644 --- a/src/67a5e0ca-0ff0-4979-9b41-da0b09988016/settings.json +++ b/src/67a5e0ca-0ff0-4979-9b41-da0b09988016/settings.json @@ -29,12 +29,16 @@ "use_selection4": { "use": "4144ad3b-2ad5-401f-af0c-a3d856c1e688", "as": "default" + }, + "use_selection5": { + "use": "f11dc790-713e-4706-9a85-a318ed3ad56e", + "as": "default" } }, "extendsinterfaces": null, "namespace": "[[[NamespacePrefix]]]\\Joomla\\[[[ComponentNamespace]]].File.Manager", "description": "File Manager Class\r\n\r\n@since 5.0.2", "licensing_template": "\/**\r\n * @package Joomla.Component.Builder\r\n *\r\n * @created 3rd September, 2020\r\n * @author Llewellyn van der Merwe \r\n * @git Joomla Component Builder \r\n * @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.\r\n * @license GNU General Public License version 2 or later; see LICENSE.txt\r\n *\/\r\n", - "head": "use Joomla\\CMS\\Language\\Text;", + "head": "use Joomla\\CMS\\Factory;\r\nuse Joomla\\CMS\\User\\User;\r\nuse Joomla\\CMS\\Language\\Text;\r\nuse Joomla\\Filesystem\\File;", "composer": "" } \ No newline at end of file diff --git a/src/d7600b43-771a-4747-9f5d-952765721799/code.php b/src/d7600b43-771a-4747-9f5d-952765721799/code.php index 8ef7004..a209783 100644 --- a/src/d7600b43-771a-4747-9f5d-952765721799/code.php +++ b/src/d7600b43-771a-4747-9f5d-952765721799/code.php @@ -193,7 +193,7 @@ abstract class UploadHelper } // set full path - $userfile['full_path'] = $userfile['path'] . '/' . $userfile['file_name']; + $userfile['full_path'] = Path::clean($userfile['path'] . '/' . $userfile['file_name']); // Upload the file. if (File::upload($userfile['tmp_name'], $userfile['full_path'], static::$useStreams, static::$allowUnsafe)) @@ -233,28 +233,34 @@ abstract class UploadHelper */ protected static function check(array $upload, string $type): ?array { - // Default formats - $formats = MimeHelper::getFileExtensions($type); + // Default extensions/formats + $extensions = MimeHelper::getFileExtensions($type); // Clean the path $upload_path = Path::clean($upload['full_path']); // Get file extension/format - $upload['extension'] = $format = MimeHelper::extension($upload_path); + $extension = MimeHelper::extension($upload_path); + $mime = $upload['type']; + + unset($upload['type']); + + // set to check + $checking_mime = MimeHelper::mimeType($upload_path); // Legal file formats $legal = []; // check if the file format is even in the list - if (in_array($format, $formats)) + if (in_array($extension, $extensions)) { // get allowed formats $legal_formats = (array) Helper::getParams()->get($type . '_formats', []); - $legal = array_values(array_unique(array_merge($legal_formats, static::$legalFormats))); + $legal_extensions = array_values(array_unique(array_merge($legal_formats, static::$legalFormats))); } // check the extension - if (!in_array($format, $legal)) + if (!in_array($extension, $legal_extensions)) { // Cleanup the import file static::remove($upload['full_path']); @@ -264,6 +270,13 @@ abstract class UploadHelper return null; } + if ($checking_mime === $mime) + { + $upload['mime'] = $mime; // TODO we should keep and eye on this. + } + + $upload['extension'] = $extension; + return $upload; } diff --git a/src/d7600b43-771a-4747-9f5d-952765721799/code.power b/src/d7600b43-771a-4747-9f5d-952765721799/code.power index 4076d81..bb0aafb 100644 --- a/src/d7600b43-771a-4747-9f5d-952765721799/code.power +++ b/src/d7600b43-771a-4747-9f5d-952765721799/code.power @@ -164,7 +164,7 @@ } // set full path - $userfile['full_path'] = $userfile['path'] . '/' . $userfile['file_name']; + $userfile['full_path'] = Path::clean($userfile['path'] . '/' . $userfile['file_name']); // Upload the file. if (File::upload($userfile['tmp_name'], $userfile['full_path'], static::$useStreams, static::$allowUnsafe)) @@ -204,28 +204,34 @@ */ protected static function check(array $upload, string $type): ?array { - // Default formats - $formats = MimeHelper::getFileExtensions($type); + // Default extensions/formats + $extensions = MimeHelper::getFileExtensions($type); // Clean the path $upload_path = Path::clean($upload['full_path']); // Get file extension/format - $upload['extension'] = $format = MimeHelper::extension($upload_path); + $extension = MimeHelper::extension($upload_path); + $mime = $upload['type']; + + unset($upload['type']); + + // set to check + $checking_mime = MimeHelper::mimeType($upload_path); // Legal file formats $legal = []; // check if the file format is even in the list - if (in_array($format, $formats)) + if (in_array($extension, $extensions)) { // get allowed formats $legal_formats = (array) Helper::getParams()->get($type . '_formats', []); - $legal = array_values(array_unique(array_merge($legal_formats, static::$legalFormats))); + $legal_extensions = array_values(array_unique(array_merge($legal_formats, static::$legalFormats))); } // check the extension - if (!in_array($format, $legal)) + if (!in_array($extension, $legal_extensions)) { // Cleanup the import file static::remove($upload['full_path']); @@ -235,6 +241,13 @@ return null; } + if ($checking_mime === $mime) + { + $upload['mime'] = $mime; // TODO we should keep and eye on this. + } + + $upload['extension'] = $extension; + return $upload; }