From d600ee7830aa23f6eb34e317658753b763ccecb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=8Carodej?= Date: Thu, 4 Nov 2021 17:37:25 +0100 Subject: [PATCH] Transfer Content Ownership To Team Folder Owner --- public/mix-manifest.json | 12 ++- .../js/components/FilesView/InfoSidebar.vue | 6 +- resources/js/helpers/itemHelpers.js | 2 +- ...TeamFolderPropertyForAllChildrenAction.php | 2 +- ...ontentOwnershipToTeamFolderOwnerAction.php | 72 ++++++++++++++++++ .../Teams/Actions/UpdateMembersAction.php | 1 + .../Controllers/LeaveTeamFolderController.php | 22 ++++-- src/Support/helpers.php | 11 +++ tests/Domain/Teams/TeamManagementTest.php | 75 +++++++++++++++++++ 9 files changed, 189 insertions(+), 14 deletions(-) create mode 100644 src/Domain/Teams/Actions/TransferContentOwnershipToTeamFolderOwnerAction.php diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 36bd8771..0ffd8251 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -59,7 +59,7 @@ "/chunks/plans.js": "/chunks/plans.js?id=6fca685daa45f22e4c8f", "/chunks/platform.js": "/chunks/platform.js?id=1863e0b77ad5c26a05bc", "/chunks/platform~chunks/settings-subscription~chunks/shared~chunks/user-subscription.js": "/chunks/platform~chunks/settings-subscription~chunks/shared~chunks/user-subscription.js?id=8656b69a97cace919251", - "/chunks/platform~chunks/shared.js": "/chunks/platform~chunks/shared.js?id=1bb410d04a3fb0bdd15c", + "/chunks/platform~chunks/shared.js": "/chunks/platform~chunks/shared.js?id=f0d10b45321b77d9fb21", "/chunks/profile.js": "/chunks/profile.js?id=022c1617a575d4aab4e1", "/chunks/profile~chunks/settings-password.js": "/chunks/profile~chunks/settings-password.js?id=58edfb3a35062e1ba4e0", "/chunks/purchase-code.js": "/chunks/purchase-code.js?id=f8b2619e393a5823bf29", @@ -1488,5 +1488,13 @@ "/chunks/shared-with-me.93d1862a2d1291f017c5.hot-update.js": "/chunks/shared-with-me.93d1862a2d1291f017c5.hot-update.js", "/chunks/team-folders.5f3b12c7626c1febc1f4.hot-update.js": "/chunks/team-folders.5f3b12c7626c1febc1f4.hot-update.js", "/chunks/team-folders.9a710c5840f0fe78b845.hot-update.js": "/chunks/team-folders.9a710c5840f0fe78b845.hot-update.js", - "/chunks/shared-with-me.de7b0068aa7d3daa54d0.hot-update.js": "/chunks/shared-with-me.de7b0068aa7d3daa54d0.hot-update.js" + "/chunks/shared-with-me.de7b0068aa7d3daa54d0.hot-update.js": "/chunks/shared-with-me.de7b0068aa7d3daa54d0.hot-update.js", + "/js/main.b96dce782e1445910175.hot-update.js": "/js/main.b96dce782e1445910175.hot-update.js", + "/js/main.d367944ae8ff81bdfd36.hot-update.js": "/js/main.d367944ae8ff81bdfd36.hot-update.js", + "/js/main.8e230d654f8e6dc09627.hot-update.js": "/js/main.8e230d654f8e6dc09627.hot-update.js", + "/js/main.254907f280bbbbaaa3cf.hot-update.js": "/js/main.254907f280bbbbaaa3cf.hot-update.js", + "/chunks/platform~chunks/shared.f81c519d38811aa1937b.hot-update.js": "/chunks/platform~chunks/shared.f81c519d38811aa1937b.hot-update.js", + "/chunks/platform~chunks/shared.1b7dcdcf4abc08a8b218.hot-update.js": "/chunks/platform~chunks/shared.1b7dcdcf4abc08a8b218.hot-update.js", + "/chunks/platform~chunks/shared.61307263989bd7deb32e.hot-update.js": "/chunks/platform~chunks/shared.61307263989bd7deb32e.hot-update.js", + "/chunks/platform~chunks/shared.3f8787077c840fc65667.hot-update.js": "/chunks/platform~chunks/shared.3f8787077c840fc65667.hot-update.js" } diff --git a/resources/js/components/FilesView/InfoSidebar.vue b/resources/js/components/FilesView/InfoSidebar.vue index de93f415..8e7b36cc 100644 --- a/resources/js/components/FilesView/InfoSidebar.vue +++ b/resources/js/components/FilesView/InfoSidebar.vue @@ -37,8 +37,8 @@ :title="$t('Author')" >
- - {{ singleFile.data.relationships.user.data.attributes.name }} + + {{ singleFile.data.relationships.owner.data.attributes.name }}
@@ -165,7 +165,7 @@ canShowAuthor() { return this.$isThisRoute(this.$route, ['SharedWithMe', 'TeamFolders']) && this.clipboard[0].data.type !== 'folder' - && this.user.data.id !== this.clipboard[0].data.relationships.user.data.id + && this.user.data.id !== this.clipboard[0].data.relationships.owner.data.id }, }, methods: { diff --git a/resources/js/helpers/itemHelpers.js b/resources/js/helpers/itemHelpers.js index d84cbace..2ba34a9b 100644 --- a/resources/js/helpers/itemHelpers.js +++ b/resources/js/helpers/itemHelpers.js @@ -48,7 +48,7 @@ const itemHelpers = { Vue.prototype.$detachMeFromTeamFolder = function (folder) { events.$emit('confirm:open', { title: this.$t('Are you sure you want to leave this team?'), - message: this.$t('You will not have access to the files in this team folder.'), + message: this.$t("You will don't have access to the files and all your previously uploaded content will be part of this Team Folder you are leaving."), action: { id: folder.data.id, operation: 'leave-team-folder', diff --git a/src/Domain/Teams/Actions/SetTeamFolderPropertyForAllChildrenAction.php b/src/Domain/Teams/Actions/SetTeamFolderPropertyForAllChildrenAction.php index 978959e7..eba6d82e 100644 --- a/src/Domain/Teams/Actions/SetTeamFolderPropertyForAllChildrenAction.php +++ b/src/Domain/Teams/Actions/SetTeamFolderPropertyForAllChildrenAction.php @@ -17,7 +17,7 @@ class SetTeamFolderPropertyForAllChildrenAction // Set all children as team_folder = true DB::table('folders') - ->whereIn('id', Arr::flatten([filter_folders_ids($childrenFolderIds)])) + ->whereIn('id', Arr::flatten(filter_folders_ids($childrenFolderIds))) ->update(['team_folder' => $isTeamFolder]); } } \ No newline at end of file diff --git a/src/Domain/Teams/Actions/TransferContentOwnershipToTeamFolderOwnerAction.php b/src/Domain/Teams/Actions/TransferContentOwnershipToTeamFolderOwnerAction.php new file mode 100644 index 00000000..2051a2ad --- /dev/null +++ b/src/Domain/Teams/Actions/TransferContentOwnershipToTeamFolderOwnerAction.php @@ -0,0 +1,72 @@ +where('parent_id', $folder->id) + ->where('user_id', $leavingUserId) + ->delete(); + + // Get all inherited folder from team folder + $childrenFolderIds = Folder::with('folders:id,parent_id') + ->where('id', $folder->id) + ->get('id'); + + $teamFolderIds = Arr::flatten(filter_folders_ids($childrenFolderIds)); + + // Replace leaver content ownership for author of team folder + DB::table('files') + ->whereIn('parent_id', $teamFolderIds) + ->where('user_id', $leavingUserId) + ->cursor() + ->each(fn ($file) => + $this->move_files_to_the_new_destination($file, $folder) + ); + + DB::table('files') + ->whereIn('parent_id', $teamFolderIds) + ->where('user_id', $leavingUserId) + ->update(['user_id' => $folder->user_id]); + + DB::table('folders') + ->whereIn('id', $teamFolderIds) + ->where('user_id', $leavingUserId) + ->update(['user_id' => $folder->user_id]); + } + + /** + * @param $file + * @param Folder $folder + */ + private function move_files_to_the_new_destination($file, Folder $folder): void + { + // Move image thumbnails + if ($file->type === 'image') { + + // Get image thumbnail list + $thumbnailList = get_thumbnail_file_list($file->basename); + + // move thumbnails to the new location + $thumbnailList->each(function ($basename) use ($file, $folder) { + + $oldPath = "files/$file->user_id/$basename"; + $newPath = "files/$folder->user_id/$basename"; + + if (Storage::exists($oldPath)) Storage::move($oldPath, $newPath); + }); + } + + // Move single file + Storage::move("files/$file->user_id/$file->basename", "files/$folder->user_id/$file->basename"); + } +} \ No newline at end of file diff --git a/src/Domain/Teams/Actions/UpdateMembersAction.php b/src/Domain/Teams/Actions/UpdateMembersAction.php index 538a9abc..b7cc31e8 100644 --- a/src/Domain/Teams/Actions/UpdateMembersAction.php +++ b/src/Domain/Teams/Actions/UpdateMembersAction.php @@ -6,6 +6,7 @@ use Domain\Folders\Models\Folder; class UpdateMembersAction { + // TODO: after removing user from team folder from administrator, set file owner public function __invoke(Folder $folder, $members): void { $existingMembers = $folder diff --git a/src/Domain/Teams/Controllers/LeaveTeamFolderController.php b/src/Domain/Teams/Controllers/LeaveTeamFolderController.php index 2ffcd5fa..a1efa8ed 100644 --- a/src/Domain/Teams/Controllers/LeaveTeamFolderController.php +++ b/src/Domain/Teams/Controllers/LeaveTeamFolderController.php @@ -1,23 +1,31 @@ where('parent_id', $folder->id) - ->where('user_id', Auth::id()) - ->delete(); + // Authorize action + if (! Gate::any(['can-edit', 'can-view'], [$folder, null])) { + abort(403, 'Access Denied'); + } + + // Transfer files/folders ownership to team folder owner + ($this->transferContentOwnership)($folder, Auth::id()); return response('Done.', 204); } diff --git a/src/Support/helpers.php b/src/Support/helpers.php index 9d491272..ec14688d 100644 --- a/src/Support/helpers.php +++ b/src/Support/helpers.php @@ -599,6 +599,17 @@ if (! function_exists('get_file_type')) { } } +if (! function_exists('get_thumbnail_file_list')) { + /** + * Get list of image thumbnails + */ + function get_thumbnail_file_list(string $basename): Collection + { + return collect(config('vuefilemanager.image_sizes')) + ->map(fn ($item) => $item['name'] . '-' . $basename); + } +} + if (! function_exists('map_language_translations')) { /** * It map language translations as language key and language value diff --git a/tests/Domain/Teams/TeamManagementTest.php b/tests/Domain/Teams/TeamManagementTest.php index e21a9fdc..c05b8c7b 100644 --- a/tests/Domain/Teams/TeamManagementTest.php +++ b/tests/Domain/Teams/TeamManagementTest.php @@ -2,6 +2,9 @@ namespace Tests\Domain\Teams; +use Domain\Files\Models\File; +use Illuminate\Http\UploadedFile; +use Illuminate\Support\Facades\Storage; use Str; use Notification; use Tests\TestCase; @@ -574,10 +577,62 @@ class TeamManagementTest extends TestCase $folder = Folder::factory() ->create([ + 'name' => 'Team Folder', 'user_id' => $user->id, 'team_folder' => 1, ]); + $folderWithin = Folder::factory() + ->create([ + 'name' => 'Member Content', + 'parent_id' => $folder->id, + 'user_id' => $member->id, + 'team_folder' => 1, + ]); + + $file = File::factory() + ->create([ + 'name' => 'Member File', + 'basename' => 'fake-file.zip', + 'parent_id' => $folderWithin->id, + 'user_id' => $member->id, + 'type' => 'file', + ]); + + // Create fake file + $fakeFile = UploadedFile::fake() + ->create('fake-file.zip', 2000, 'application/zip'); + + // Put fake file into correct directory + Storage::putFileAs("files/$member->id", $fakeFile, 'fake-file.zip'); + + File::factory() + ->create([ + 'name' => 'Good image', + 'basename' => 'fake-image.jpeg', + 'parent_id' => $folderWithin->id, + 'user_id' => $member->id, + 'type' => 'image', + ]); + + // Create fake image + $fakeFile = UploadedFile::fake() + ->create("fake-image.jpeg", 2000, 'image/jpeg'); + + // Put fake image into correct directory + Storage::putFileAs("files/$member->id", $fakeFile, $fakeFile->name); + + // Create fake image thumbnails + collect(config('vuefilemanager.image_sizes')) + ->each(function ($item) use ($member) { + + $fakeFile = UploadedFile::fake() + ->create("{$item['name']}-fake-image.jpeg", 2000, 'image/jpeg'); + + Storage::putFileAs("files/$member->id", $fakeFile, $fakeFile->name); + }); + + // add member to the team folder DB::table('team_folder_members') ->insert([ [ @@ -592,11 +647,31 @@ class TeamManagementTest extends TestCase ->deleteJson("/api/teams/folders/{$folder->id}/leave") ->assertNoContent(); + // Check if file was moved from member directory to owner directory + Storage::assertMissing("files/$member->id/fake-file.zip"); + Storage::assertExists("files/$user->id/fake-file.zip"); + + // Assert if image thumbnails was moved correctly to the new destination + collect(config('vuefilemanager.image_sizes')) + ->each(function ($item) use ($user) { + Storage::assertExists("files/$user->id/{$item['name']}-fake-image.jpeg"); + }); + $this ->assertDatabaseMissing('team_folder_members', [ 'parent_id' => $folder->id, 'user_id' => $member->id, 'permission' => 'can-edit', + ]) + ->assertDatabaseHas('files', [ + 'id' => $file->id, + 'parent_id' => $folderWithin->id, + 'user_id' => $user->id, + ]) + ->assertDatabaseHas('folders', [ + 'id' => $folderWithin->id, + 'parent_id' => $folder->id, + 'user_id' => $user->id, ]); }