middleware('auth'); $this->middleware('hasConfig:groups.enabled'); } public function index(Request $request) { abort_if(! $request->user(), 404); return view('layouts.spa'); } public function home(Request $request) { abort_if(! $request->user(), 404); return view('layouts.spa'); } public function show(Request $request, $id, $path = false) { $group = Group::find($id); if (! $group || $group->status) { return response()->view('groups.unavailable')->setStatusCode(404); } if ($request->wantsJson()) { return $this->showGroupObject($group); } return view('layouts.spa', compact('id', 'path')); } public function showStatus(Request $request, $gid, $sid) { $group = Group::find($gid); $pid = optional($request->user())->profile_id ?? false; if (! $group || $group->status) { return response()->view('groups.unavailable')->setStatusCode(404); } if ($group->is_private) { abort_if(! $request->user(), 404); abort_if(! $group->isMember($pid), 404); } $gp = GroupPost::whereGroupId($gid) ->findOrFail($sid); return view('layouts.spa', compact('group', 'gp')); } public function getGroup(Request $request, $id) { $group = Group::whereNull('status')->findOrFail($id); $pid = optional($request->user())->profile_id ?? false; $group = $this->toJson($group, $pid); return response()->json($group, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); } public function showStatusLikes(Request $request, $id, $sid) { $group = Group::findOrFail($id); $user = $request->user(); $pid = $user->profile_id; abort_if(! $group->isMember($pid), 404); $status = GroupPost::whereGroupId($id)->findOrFail($sid); $likes = GroupLike::whereStatusId($sid) ->cursorPaginate(10) ->map(function ($l) use ($group) { $account = AccountService::get($l->profile_id); $account['url'] = "/groups/{$group->id}/user/{$account['id']}"; return $account; }) ->filter(function ($l) { return $l && isset($l['id']); }) ->values(); return $likes; } public function groupSettings(Request $request, $id) { abort_if(! $request->user(), 404); $group = Group::findOrFail($id); $pid = $request->user()->profile_id; abort_if(! $group->isMember($pid), 404); abort_if(! in_array($group->selfRole($pid), ['founder', 'admin']), 404); return view('groups.settings', compact('group')); } public function joinGroup(Request $request, $id) { $group = Group::findOrFail($id); $pid = $request->user()->profile_id; abort_if($group->isMember($pid), 404); if (! $request->user()->is_admin) { abort_if(GroupService::getRejoinTimeout($group->id, $pid), 422, 'Cannot re-join this group for 24 hours after leaving or cancelling a request to join'); } $member = new GroupMember; $member->group_id = $group->id; $member->profile_id = $pid; $member->role = 'member'; $member->local_group = true; $member->local_profile = true; $member->join_request = $group->is_private; $member->save(); GroupService::delSelf($group->id, $pid); GroupService::log( $group->id, $pid, 'group:joined', null, GroupMember::class, $member->id ); $group = $this->toJson($group, $pid); return $group; } public function updateGroup(Request $request, $id) { $this->validate($request, [ 'description' => 'nullable|max:500', 'membership' => 'required|in:all,local,private', 'avatar' => 'nullable', 'header' => 'nullable', 'discoverable' => 'required', 'activitypub' => 'required', 'is_nsfw' => 'required', 'category' => 'required|string|in:'.implode(',', GroupService::categories()), ]); $pid = $request->user()->profile_id; $group = Group::whereProfileId($pid)->findOrFail($id); $member = GroupMember::whereGroupId($group->id)->whereProfileId($pid)->firstOrFail(); abort_if($member->role != 'founder', 403, 'Invalid group permission'); $metadata = $group->metadata; $len = $group->is_private ? 12 : 4; if ($request->hasFile('avatar')) { $avatar = $request->file('avatar'); if ($avatar) { if (isset($metadata['avatar']) && isset($metadata['avatar']['path']) && Storage::exists($metadata['avatar']['path']) ) { Storage::delete($metadata['avatar']['path']); } $fileName = 'avatar_'.strtolower(str_random($len)).'.'.$avatar->extension(); $path = $avatar->storePubliclyAs('public/g/'.$group->id.'/meta', $fileName); $url = url(Storage::url($path)); $metadata['avatar'] = [ 'path' => $path, 'url' => $url, 'updated_at' => now(), ]; } } if ($request->hasFile('header')) { $header = $request->file('header'); if ($header) { if (isset($metadata['header']) && isset($metadata['header']['path']) && Storage::exists($metadata['header']['path']) ) { Storage::delete($metadata['header']['path']); } $fileName = 'header_'.strtolower(str_random($len)).'.'.$header->extension(); $path = $header->storePubliclyAs('public/g/'.$group->id.'/meta', $fileName); $url = url(Storage::url($path)); $metadata['header'] = [ 'path' => $path, 'url' => $url, 'updated_at' => now(), ]; } } $cat = GroupService::categoryById($group->category_id); if ($request->category !== $cat['name']) { $group->category_id = GroupCategory::whereName($request->category)->first()->id; } $changes = null; $group->description = e($request->input('description', null)); $group->is_private = $request->input('membership') == 'private'; $group->local_only = $request->input('membership') == 'local'; $group->activitypub = $request->input('activitypub') == 'true'; $group->discoverable = $request->input('discoverable') == 'true'; $group->is_nsfw = $request->input('is_nsfw') == 'true'; $group->metadata = $metadata; if ($group->isDirty()) { $changes = $group->getDirty(); } $group->save(); GroupService::log( $group->id, $pid, 'group:settings:updated', $changes ); GroupService::del($group->id); $res = $this->toJson($group, $pid); return $res; } protected function toJson($group, $pid = false) { return GroupService::get($group->id, $pid); } public function groupLeave(Request $request, $id) { abort_if(! $request->user(), 404); $pid = $request->user()->profile_id; $group = Group::findOrFail($id); abort_if($pid == $group->profile_id, 422, 'Cannot leave a group you created'); abort_if(! $group->isMember($pid), 403, 'Not a member of group.'); GroupMember::whereGroupId($group->id)->whereProfileId($pid)->delete(); GroupService::del($group->id); GroupService::delSelf($group->id, $pid); GroupService::setRejoinTimeout($group->id, $pid); return [200]; } public function cancelJoinRequest(Request $request, $id) { abort_if(! $request->user(), 404); $pid = $request->user()->profile_id; $group = Group::findOrFail($id); abort_if($pid == $group->profile_id, 422, 'Cannot leave a group you created'); abort_if($group->isMember($pid), 422, 'Cannot cancel approved join request, please leave group instead.'); GroupMember::whereGroupId($group->id)->whereProfileId($pid)->delete(); GroupService::del($group->id); GroupService::delSelf($group->id, $pid); GroupService::setRejoinTimeout($group->id, $pid); return [200]; } public function metaBlockSearch(Request $request, $id) { abort_if(! $request->user(), 404); $group = Group::findOrFail($id); $pid = $request->user()->profile_id; abort_if(! $group->isMember($pid), 404); abort_if(! in_array($group->selfRole($pid), ['founder', 'admin']), 404); $type = $request->input('type'); $item = $request->input('item'); switch ($type) { case 'instance': $res = Instance::whereDomain($item)->first(); if ($res) { abort_if(GroupBlock::whereGroupId($group->id)->whereInstanceId($res->id)->exists(), 400); } break; case 'user': $res = Profile::whereUsername($item)->first(); if ($res) { abort_if(GroupBlock::whereGroupId($group->id)->whereProfileId($res->id)->exists(), 400); } if ($res->user_id != null) { abort_if(User::whereIsAdmin(true)->whereId($res->user_id)->exists(), 400); } break; } return response()->json((bool) $res, ($res ? 200 : 404)); } public function reportCreate(Request $request, $id) { abort_if(! $request->user(), 404); $group = Group::findOrFail($id); $pid = $request->user()->profile_id; abort_if(! $group->isMember($pid), 404); $id = $request->input('id'); $type = $request->input('type'); $types = [ // original 3 'spam', 'sensitive', 'abusive', // new 'underage', 'violence', 'copyright', 'impersonation', 'scam', 'terrorism', ]; $gp = GroupPost::whereGroupId($group->id)->find($id); abort_if(! $gp, 422, 'Cannot report an invalid or deleted post'); abort_if(! in_array($type, $types), 422, 'Invalid report type'); abort_if($gp->profile_id === $pid, 422, 'Cannot report your own post'); abort_if( GroupReport::whereGroupId($group->id) ->whereProfileId($pid) ->whereItemType(GroupPost::class) ->whereItemId($id) ->exists(), 422, 'You already reported this' ); $report = new GroupReport(); $report->group_id = $group->id; $report->profile_id = $pid; $report->type = $type; $report->item_type = GroupPost::class; $report->item_id = $id; $report->open = true; $report->save(); GroupService::log( $group->id, $pid, 'group:report:create', [ 'type' => $type, 'report_id' => $report->id, 'status_id' => $gp->status_id, 'profile_id' => $gp->profile_id, 'username' => optional(AccountService::get($gp->profile_id))['acct'], 'gpid' => $gp->id, 'url' => $gp->url(), ], GroupReport::class, $report->id ); return response([200]); } public function reportAction(Request $request, $id) { abort_if(! $request->user(), 404); $group = Group::findOrFail($id); $pid = $request->user()->profile_id; abort_if(! $group->isMember($pid), 404); abort_if(! in_array($group->selfRole($pid), ['founder', 'admin']), 404); $this->validate($request, [ 'action' => 'required|in:cw,delete,ignore', 'id' => 'required|string', ]); $action = $request->input('action'); $id = $request->input('id'); $report = GroupReport::whereGroupId($group->id) ->findOrFail($id); $status = Status::findOrFail($report->item_id); $gp = GroupPost::whereGroupId($group->id) ->whereStatusId($status->id) ->firstOrFail(); switch ($action) { case 'cw': $status->is_nsfw = true; $status->save(); StatusService::del($status->id); GroupReport::whereGroupId($group->id) ->whereItemType($report->item_type) ->whereItemId($report->item_id) ->update(['open' => false]); GroupService::log( $group->id, $pid, 'group:moderation:action', [ 'type' => 'cw', 'report_id' => $report->id, 'status_id' => $status->id, 'profile_id' => $status->profile_id, 'status_url' => $gp->url(), ], GroupReport::class, $report->id ); return response()->json([200]); break; case 'ignore': GroupReport::whereGroupId($group->id) ->whereItemType($report->item_type) ->whereItemId($report->item_id) ->update(['open' => false]); GroupService::log( $group->id, $pid, 'group:moderation:action', [ 'type' => 'ignore', 'report_id' => $report->id, 'status_id' => $status->id, 'profile_id' => $status->profile_id, 'status_url' => $gp->url(), ], GroupReport::class, $report->id ); return response()->json([200]); break; } } public function getMemberInteractionLimits(Request $request, $id) { abort_if(! $request->user(), 404); $group = Group::findOrFail($id); $pid = $request->user()->profile_id; abort_if(! $group->isMember($pid), 404); abort_if(! in_array($group->selfRole($pid), ['founder', 'admin']), 404); $profile_id = $request->input('profile_id'); abort_if(! $group->isMember($profile_id), 404); $limits = GroupService::getInteractionLimits($group->id, $profile_id); return response()->json($limits); } public function updateMemberInteractionLimits(Request $request, $id) { abort_if(! $request->user(), 404); $group = Group::findOrFail($id); $pid = $request->user()->profile_id; abort_if(! $group->isMember($pid), 404); abort_if(! in_array($group->selfRole($pid), ['founder', 'admin']), 404); $this->validate($request, [ 'profile_id' => 'required|exists:profiles,id', 'can_post' => 'required', 'can_comment' => 'required', 'can_like' => 'required', ]); $member = $request->input('profile_id'); $can_post = $request->input('can_post'); $can_comment = $request->input('can_comment'); $can_like = $request->input('can_like'); $account = AccountService::get($member); abort_if(! $account, 422, 'Invalid profile'); abort_if(! $group->isMember($member), 422, 'Invalid profile'); $limit = GroupLimit::firstOrCreate([ 'profile_id' => $member, 'group_id' => $group->id, ]); if ($limit->wasRecentlyCreated) { abort_if(GroupLimit::whereGroupId($group->id)->count() >= 25, 422, 'limit_reached'); } $previousLimits = $limit->limits; $limit->limits = [ 'can_post' => $can_post, 'can_comment' => $can_comment, 'can_like' => $can_like, ]; $limit->save(); GroupService::clearInteractionLimits($group->id, $member); GroupService::log( $group->id, $pid, 'group:member-limits:updated', [ 'profile_id' => $account['id'], 'username' => $account['username'], 'previousLimits' => $previousLimits, 'newLimits' => $limit->limits, ], GroupLimit::class, $limit->id ); return $request->all(); } public function showProfile(Request $request, $id, $pid) { $group = Group::find($id); if (! $group || $group->status) { return response()->view('groups.unavailable')->setStatusCode(404); } return view('layouts.spa'); } public function showProfileByUsername(Request $request, $id, $pid) { abort_if(! $request->user(), 404); if (! $request->user()) { return redirect("/{$pid}"); } $group = Group::find($id); $cid = $request->user()->profile_id; if (! $group || $group->status) { return response()->view('groups.unavailable')->setStatusCode(404); } if (! $group->isMember($cid)) { return redirect("/{$pid}"); } $profile = Profile::whereUsername($pid)->first(); if (! $group->isMember($profile->id)) { return redirect("/{$pid}"); } if ($profile) { $url = url("/groups/{$id}/user/{$profile->id}"); return redirect($url); } abort(404, 'Invalid username'); } public function groupInviteLanding(Request $request, $id) { abort(404, 'Not yet implemented'); $group = Group::findOrFail($id); return view('groups.invite', compact('group')); } public function groupShortLinkRedirect(Request $request, $hid) { $gid = HashidService::decode($hid); $group = Group::findOrFail($gid); return redirect($group->url()); } public function groupInviteClaim(Request $request, $id) { $group = GroupService::get($id); abort_if(! $group || empty($group), 404); return view('groups.invite-claim', compact('group')); } public function groupMemberInviteCheck(Request $request, $id) { abort_if(! $request->user(), 404); $pid = $request->user()->profile_id; $group = Group::findOrFail($id); abort_if($group->isMember($pid), 422, 'Already a member'); $exists = GroupInvitation::whereGroupId($id)->whereToProfileId($pid)->exists(); return response()->json([ 'gid' => $id, 'can_join' => (bool) $exists, ]); } public function groupMemberInviteAccept(Request $request, $id) { abort_if(! $request->user(), 404); $pid = $request->user()->profile_id; $group = Group::findOrFail($id); abort_if($group->isMember($pid), 422, 'Already a member'); abort_if(! GroupInvitation::whereGroupId($id)->whereToProfileId($pid)->exists(), 422); $gm = new GroupMember; $gm->group_id = $id; $gm->profile_id = $pid; $gm->role = 'member'; $gm->local_group = $group->local; $gm->local_profile = true; $gm->join_request = false; $gm->save(); GroupInvitation::whereGroupId($id)->whereToProfileId($pid)->delete(); GroupService::del($id); GroupService::delSelf($id, $pid); return ['next_url' => $group->url()]; } public function groupMemberInviteDecline(Request $request, $id) { abort_if(! $request->user(), 404); $pid = $request->user()->profile_id; $group = Group::findOrFail($id); abort_if($group->isMember($pid), 422, 'Already a member'); return ['next_url' => '/']; } }