123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771 |
- <?php
- namespace App\Http\Controllers;
- use DB;
- use Illuminate\Http\Request;
- use Illuminate\Support\Str;
- use App\Models\Group;
- use App\Models\GroupActivityGraph;
- use App\Models\GroupBlock;
- use App\Models\GroupCategory;
- use App\Models\GroupComment;
- use App\Models\GroupEvent;
- use App\Models\GroupInteraction;
- use App\Models\GroupInvitation;
- use App\Models\GroupLimit;
- use App\Models\GroupLike;
- use App\Models\GroupMember;
- use App\Models\GroupPost;
- use App\Models\GroupPostHashtag;
- use App\Models\GroupReport;
- use App\Models\GroupRole;
- use App\Models\GroupStore;
- use App\Models\Poll;
- use App\Follower;
- use App\Instance;
- use App\Hashtag;
- use App\StatusHashtag;
- use App\Like;
- use App\Media;
- use App\Notification;
- use App\Profile;
- use App\Status;
- use App\User;
- use App\Util\Lexer\Autolink;
- use App\Services\AccountService;
- use App\Services\FollowerService;
- use App\Services\HashidService;
- use App\Services\LikeService;
- use App\Services\Groups\GroupCommentService;
- use App\Services\Groups\GroupsLikeService;
- use App\Services\HashtagService;
- use App\Services\GroupService;
- use App\Services\GroupFeedService;
- use App\Services\GroupPostService;
- use App\Services\PollService;
- use App\Services\RelationshipService;
- use App\Services\StatusService;
- use App\Services\UserFilterService;
- use Cache;
- use Storage;
- use Purify;
- use App\Jobs\GroupPipeline\LikePipeline;
- use App\Jobs\GroupPipeline\UnlikePipeline;
- use App\Jobs\ImageOptimizePipeline\ImageOptimize;
- use App\Jobs\VideoPipeline\VideoThumbnail;
- use App\Jobs\StatusPipeline\StatusDelete;
- use App\Jobs\GroupPipeline\GroupCommentPipeline;
- use App\Jobs\GroupPipeline\GroupMemberInvite;
- use App\Jobs\GroupPipeline\NewStatusPipeline;
- use App\Jobs\GroupPipeline\JoinApproved;
- use App\Jobs\GroupPipeline\JoinRejected;
- use Illuminate\Support\Facades\RateLimiter;
- class GroupController extends GroupFederationController
- {
- public function __construct()
- {
- // $this->middleware('auth');
- }
- 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 likePost(Request $request)
- // {
- // $this->validate($request, [
- // 'gid' => 'required|exists:groups,id',
- // 'sid' => 'required|exists:group_posts,id'
- // ]);
- // $pid = $request->user()->profile_id;
- // $gid = $request->input('gid');
- // $sid = $request->input('sid');
- // $group = Group::findOrFail($gid);
- // abort_if(!GroupService::canLike($gid, $pid), 422, 'You cannot interact with this content at this time');
- // abort_if(!$group->isMember($pid), 403, 'Not a member of group.');
- // $gp = GroupPost::whereGroupId($group->id)->findOrFail($sid);
- // $action = false;
- // if (GroupLike::whereGroupId($gid)->whereStatusId($sid)->whereProfileId($pid)->exists()) {
- // $like = GroupLike::whereProfileId($pid)->whereStatusId($sid)->firstOrFail();
- // // UnlikePipeline::dispatch($like);
- // $count = $gp->likes_count - 1;
- // $action = 'group:unlike';
- // } else {
- // $count = $gp->likes_count;
- // $like = GroupLike::firstOrCreate([
- // 'group_id' => $gid,
- // 'profile_id' => $pid,
- // 'status_id' => $sid
- // ]);
- // if($like->wasRecentlyCreated == true) {
- // $count++;
- // $gp->likes_count = $count;
- // $like->save();
- // $gp->save();
- // // LikePipeline::dispatch($like);
- // $action = 'group:like';
- // }
- // }
- // if($action) {
- // GroupService::log(
- // $group->id,
- // $pid,
- // $action,
- // [
- // 'type' => $gp->type,
- // 'status_id' => $gp->id
- // ],
- // GroupPost::class,
- // $gp->id
- // );
- // }
- // // Cache::forget('status:'.$status->id.':likedby:userid:'.$request->user()->id);
- // // StatusService::del($status->id);
- // $response = ['code' => 200, 'msg' => 'Like saved', 'count' => $count];
- // return $response;
- // }
- 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);
- }
- // $gm = GroupMember::whereGroupId($id)
- // ->whereProfileId($pid)
- // ->firstOrFail();
- // $group = json_encode(GroupService::get($id));
- // $profile = AccountService::get($pid);
- // $profile['group'] = [
- // 'joined' => $gm->created_at->format('M d, Y'),
- // 'role' => $gm->role
- // ];
- // $profile['relationship'] = RelationshipService::get($cid, $pid);
- // $profile = json_encode($profile);
- 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' => '/'];
- }
- }
|