AdminReportController.php 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521
  1. <?php
  2. namespace App\Http\Controllers\Admin;
  3. use App\AccountInterstitial;
  4. use App\Http\Resources\AdminReport;
  5. use App\Http\Resources\AdminRemoteReport;
  6. use App\Http\Resources\AdminSpamReport;
  7. use App\Jobs\DeletePipeline\DeleteAccountPipeline;
  8. use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
  9. use App\Jobs\StatusPipeline\RemoteStatusDelete;
  10. use App\Jobs\StatusPipeline\StatusDelete;
  11. use App\Jobs\StoryPipeline\StoryDelete;
  12. use App\Notification;
  13. use App\Profile;
  14. use App\Report;
  15. use App\Models\RemoteReport;
  16. use App\Services\AccountService;
  17. use App\Services\ModLogService;
  18. use App\Services\NetworkTimelineService;
  19. use App\Services\NotificationService;
  20. use App\Services\PublicTimelineService;
  21. use App\Services\StatusService;
  22. use App\Status;
  23. use App\Story;
  24. use App\User;
  25. use Cache;
  26. use Storage;
  27. use Carbon\Carbon;
  28. use Illuminate\Http\Request;
  29. use Illuminate\Support\Facades\DB;
  30. use Illuminate\Support\Facades\Redis;
  31. trait AdminReportController
  32. {
  33. public function reports(Request $request)
  34. {
  35. $filter = $request->input('filter') == 'closed' ? 'closed' : 'open';
  36. $page = $request->input('page') ?? 1;
  37. $ai = Cache::remember('admin-dash:reports:ai-count', 3600, function () {
  38. return AccountInterstitial::whereNotNull('appeal_requested_at')->whereNull('appeal_handled_at')->count();
  39. });
  40. $spam = Cache::remember('admin-dash:reports:spam-count', 3600, function () {
  41. return AccountInterstitial::whereType('post.autospam')->whereNull('appeal_handled_at')->count();
  42. });
  43. $mailVerifications = Redis::scard('email:manual');
  44. if ($filter == 'open' && $page == 1) {
  45. $reports = Cache::remember('admin-dash:reports:list-cache', 300, function () use ($filter) {
  46. return Report::whereHas('status')
  47. ->whereHas('reportedUser')
  48. ->whereHas('reporter')
  49. ->orderBy('created_at', 'desc')
  50. ->when($filter, function ($q, $filter) {
  51. return $filter == 'open' ?
  52. $q->whereNull('admin_seen') :
  53. $q->whereNotNull('admin_seen');
  54. })
  55. ->paginate(6);
  56. });
  57. } else {
  58. $reports = Report::whereHas('status')
  59. ->whereHas('reportedUser')
  60. ->whereHas('reporter')
  61. ->orderBy('created_at', 'desc')
  62. ->when($filter, function ($q, $filter) {
  63. return $filter == 'open' ?
  64. $q->whereNull('admin_seen') :
  65. $q->whereNotNull('admin_seen');
  66. })
  67. ->paginate(6);
  68. }
  69. return view('admin.reports.home', compact('reports', 'ai', 'spam', 'mailVerifications'));
  70. }
  71. public function showReport(Request $request, $id)
  72. {
  73. $report = Report::with('status')->findOrFail($id);
  74. if ($request->has('ref') && $request->input('ref') == 'email') {
  75. return redirect('/i/admin/reports?tab=report&id='.$report->id);
  76. }
  77. return view('admin.reports.show', compact('report'));
  78. }
  79. public function appeals(Request $request)
  80. {
  81. $appeals = AccountInterstitial::whereNotNull('appeal_requested_at')
  82. ->whereNull('appeal_handled_at')
  83. ->latest()
  84. ->paginate(6);
  85. return view('admin.reports.appeals', compact('appeals'));
  86. }
  87. public function showAppeal(Request $request, $id)
  88. {
  89. $appeal = AccountInterstitial::whereNotNull('appeal_requested_at')
  90. ->whereNull('appeal_handled_at')
  91. ->findOrFail($id);
  92. $meta = json_decode($appeal->meta);
  93. return view('admin.reports.show_appeal', compact('appeal', 'meta'));
  94. }
  95. public function spam(Request $request)
  96. {
  97. $this->validate($request, [
  98. 'tab' => 'sometimes|in:home,not-spam,spam,settings,custom,exemptions',
  99. ]);
  100. $tab = $request->input('tab', 'home');
  101. $openCount = Cache::remember('admin-dash:reports:spam-count', 3600, function () {
  102. return AccountInterstitial::whereType('post.autospam')
  103. ->whereNull('appeal_handled_at')
  104. ->count();
  105. });
  106. $monthlyCount = Cache::remember('admin-dash:reports:spam-count:30d', 43200, function () {
  107. return AccountInterstitial::whereType('post.autospam')
  108. ->where('created_at', '>', now()->subMonth())
  109. ->count();
  110. });
  111. $totalCount = Cache::remember('admin-dash:reports:spam-count:total', 43200, function () {
  112. return AccountInterstitial::whereType('post.autospam')->count();
  113. });
  114. $uncategorized = Cache::remember('admin-dash:reports:spam-sync', 3600, function () {
  115. return AccountInterstitial::whereType('post.autospam')
  116. ->whereIsSpam(null)
  117. ->whereNotNull('appeal_handled_at')
  118. ->exists();
  119. });
  120. $avg = Cache::remember('admin-dash:reports:spam-count:avg', 43200, function () {
  121. if (config('database.default') != 'mysql') {
  122. return 0;
  123. }
  124. return AccountInterstitial::selectRaw('*, count(id) as counter')
  125. ->whereType('post.autospam')
  126. ->groupBy('user_id')
  127. ->get()
  128. ->avg('counter');
  129. });
  130. $avgOpen = Cache::remember('admin-dash:reports:spam-count:avgopen', 43200, function () {
  131. if (config('database.default') != 'mysql') {
  132. return '0';
  133. }
  134. $seconds = AccountInterstitial::selectRaw('DATE(created_at) AS start_date, AVG(TIME_TO_SEC(TIMEDIFF(appeal_handled_at, created_at))) AS timediff')->whereType('post.autospam')->whereNotNull('appeal_handled_at')->where('created_at', '>', now()->subMonth())->get();
  135. if (! $seconds) {
  136. return '0';
  137. }
  138. $mins = floor($seconds->avg('timediff') / 60);
  139. if ($mins < 60) {
  140. return $mins.' min(s)';
  141. }
  142. if ($mins < 2880) {
  143. return floor($mins / 60).' hour(s)';
  144. }
  145. return floor($mins / 60 / 24).' day(s)';
  146. });
  147. $avgCount = $totalCount && $avg ? floor($totalCount / $avg) : '0';
  148. if (in_array($tab, ['home', 'spam', 'not-spam'])) {
  149. $appeals = AccountInterstitial::whereType('post.autospam')
  150. ->when($tab, function ($q, $tab) {
  151. switch ($tab) {
  152. case 'home':
  153. return $q->whereNull('appeal_handled_at');
  154. break;
  155. case 'spam':
  156. return $q->whereIsSpam(true);
  157. break;
  158. case 'not-spam':
  159. return $q->whereIsSpam(false);
  160. break;
  161. }
  162. })
  163. ->latest()
  164. ->paginate(6);
  165. if ($tab !== 'home') {
  166. $appeals = $appeals->appends(['tab' => $tab]);
  167. }
  168. } else {
  169. $appeals = new class
  170. {
  171. public function count()
  172. {
  173. return 0;
  174. }
  175. public function render()
  176. {
  177. }
  178. };
  179. }
  180. return view('admin.reports.spam', compact('tab', 'appeals', 'openCount', 'monthlyCount', 'totalCount', 'avgCount', 'avgOpen', 'uncategorized'));
  181. }
  182. public function showSpam(Request $request, $id)
  183. {
  184. $appeal = AccountInterstitial::whereType('post.autospam')
  185. ->findOrFail($id);
  186. if ($request->has('ref') && $request->input('ref') == 'email') {
  187. return redirect('/i/admin/reports?tab=autospam&id='.$appeal->id);
  188. }
  189. $meta = json_decode($appeal->meta);
  190. return view('admin.reports.show_spam', compact('appeal', 'meta'));
  191. }
  192. public function fixUncategorizedSpam(Request $request)
  193. {
  194. if (Cache::get('admin-dash:reports:spam-sync-active')) {
  195. return redirect('/i/admin/reports/autospam');
  196. }
  197. Cache::put('admin-dash:reports:spam-sync-active', 1, 900);
  198. AccountInterstitial::chunk(500, function ($reports) {
  199. foreach ($reports as $report) {
  200. if ($report->item_type != 'App\Status') {
  201. continue;
  202. }
  203. if ($report->type != 'post.autospam') {
  204. continue;
  205. }
  206. if ($report->is_spam != null) {
  207. continue;
  208. }
  209. $status = StatusService::get($report->item_id, false);
  210. if (! $status) {
  211. return;
  212. }
  213. $scope = $status['visibility'];
  214. $report->is_spam = $scope == 'unlisted';
  215. $report->in_violation = $report->is_spam;
  216. $report->severity_index = 1;
  217. $report->save();
  218. }
  219. });
  220. Cache::forget('admin-dash:reports:spam-sync');
  221. return redirect('/i/admin/reports/autospam');
  222. }
  223. public function updateSpam(Request $request, $id)
  224. {
  225. $this->validate($request, [
  226. 'action' => 'required|in:dismiss,approve,dismiss-all,approve-all,delete-account,mark-spammer',
  227. ]);
  228. $action = $request->input('action');
  229. $appeal = AccountInterstitial::whereType('post.autospam')
  230. ->whereNull('appeal_handled_at')
  231. ->findOrFail($id);
  232. $meta = json_decode($appeal->meta);
  233. $res = ['status' => 'success'];
  234. $now = now();
  235. Cache::forget('admin-dash:reports:spam-count:total');
  236. Cache::forget('admin-dash:reports:spam-count:30d');
  237. if ($action == 'delete-account') {
  238. if (config('pixelfed.account_deletion') == false) {
  239. abort(404);
  240. }
  241. $user = User::findOrFail($appeal->user_id);
  242. $profile = $user->profile;
  243. if ($user->is_admin == true) {
  244. $mid = $request->user()->id;
  245. abort_if($user->id < $mid, 403);
  246. }
  247. $ts = now()->addMonth();
  248. $user->status = 'delete';
  249. $profile->status = 'delete';
  250. $user->delete_after = $ts;
  251. $profile->delete_after = $ts;
  252. $user->save();
  253. $profile->save();
  254. ModLogService::boot()
  255. ->objectUid($user->id)
  256. ->objectId($user->id)
  257. ->objectType('App\User::class')
  258. ->user($request->user())
  259. ->action('admin.user.delete')
  260. ->accessLevel('admin')
  261. ->save();
  262. Cache::forget('profiles:private');
  263. DeleteAccountPipeline::dispatch($user);
  264. return;
  265. }
  266. if ($action == 'dismiss') {
  267. $appeal->is_spam = true;
  268. $appeal->appeal_handled_at = $now;
  269. $appeal->save();
  270. Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$appeal->user->profile_id);
  271. Cache::forget('pf:bouncer_v0:recent_by_pid:'.$appeal->user->profile_id);
  272. Cache::forget('admin-dash:reports:spam-count');
  273. return $res;
  274. }
  275. if ($action == 'dismiss-all') {
  276. AccountInterstitial::whereType('post.autospam')
  277. ->whereItemType('App\Status')
  278. ->whereNull('appeal_handled_at')
  279. ->whereUserId($appeal->user_id)
  280. ->update(['appeal_handled_at' => $now, 'is_spam' => true]);
  281. Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$appeal->user->profile_id);
  282. Cache::forget('pf:bouncer_v0:recent_by_pid:'.$appeal->user->profile_id);
  283. Cache::forget('admin-dash:reports:spam-count');
  284. return $res;
  285. }
  286. if ($action == 'approve-all') {
  287. AccountInterstitial::whereType('post.autospam')
  288. ->whereItemType('App\Status')
  289. ->whereNull('appeal_handled_at')
  290. ->whereUserId($appeal->user_id)
  291. ->get()
  292. ->each(function ($report) use ($meta) {
  293. $report->is_spam = false;
  294. $report->appeal_handled_at = now();
  295. $report->save();
  296. $status = Status::find($report->item_id);
  297. if ($status) {
  298. $status->is_nsfw = $meta->is_nsfw;
  299. $status->scope = 'public';
  300. $status->visibility = 'public';
  301. $status->save();
  302. StatusService::del($status->id, true);
  303. }
  304. });
  305. Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$appeal->user->profile_id);
  306. Cache::forget('pf:bouncer_v0:recent_by_pid:'.$appeal->user->profile_id);
  307. Cache::forget('admin-dash:reports:spam-count');
  308. return $res;
  309. }
  310. if ($action == 'mark-spammer') {
  311. AccountInterstitial::whereType('post.autospam')
  312. ->whereItemType('App\Status')
  313. ->whereNull('appeal_handled_at')
  314. ->whereUserId($appeal->user_id)
  315. ->update(['appeal_handled_at' => $now, 'is_spam' => true]);
  316. $pro = Profile::whereUserId($appeal->user_id)->firstOrFail();
  317. $pro->update([
  318. 'unlisted' => true,
  319. 'cw' => true,
  320. 'no_autolink' => true,
  321. ]);
  322. Status::whereProfileId($pro->id)
  323. ->get()
  324. ->each(function ($report) {
  325. $status->is_nsfw = $meta->is_nsfw;
  326. $status->scope = 'public';
  327. $status->visibility = 'public';
  328. $status->save();
  329. StatusService::del($status->id, true);
  330. });
  331. Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$appeal->user->profile_id);
  332. Cache::forget('pf:bouncer_v0:recent_by_pid:'.$appeal->user->profile_id);
  333. Cache::forget('admin-dash:reports:spam-count');
  334. return $res;
  335. }
  336. $status = $appeal->status;
  337. $status->is_nsfw = $meta->is_nsfw;
  338. $status->scope = 'public';
  339. $status->visibility = 'public';
  340. $status->save();
  341. $appeal->is_spam = false;
  342. $appeal->appeal_handled_at = now();
  343. $appeal->save();
  344. StatusService::del($status->id);
  345. Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$appeal->user->profile_id);
  346. Cache::forget('pf:bouncer_v0:recent_by_pid:'.$appeal->user->profile_id);
  347. Cache::forget('admin-dash:reports:spam-count');
  348. return $res;
  349. }
  350. public function updateAppeal(Request $request, $id)
  351. {
  352. $this->validate($request, [
  353. 'action' => 'required|in:dismiss,approve',
  354. ]);
  355. $action = $request->input('action');
  356. $appeal = AccountInterstitial::whereNotNull('appeal_requested_at')
  357. ->whereNull('appeal_handled_at')
  358. ->findOrFail($id);
  359. if ($action == 'dismiss') {
  360. $appeal->appeal_handled_at = now();
  361. $appeal->save();
  362. Cache::forget('admin-dash:reports:ai-count');
  363. return redirect('/i/admin/reports/appeals');
  364. }
  365. switch ($appeal->type) {
  366. case 'post.cw':
  367. $status = $appeal->status;
  368. $status->is_nsfw = false;
  369. $status->save();
  370. break;
  371. case 'post.unlist':
  372. $status = $appeal->status;
  373. $status->scope = 'public';
  374. $status->visibility = 'public';
  375. $status->save();
  376. break;
  377. default:
  378. // code...
  379. break;
  380. }
  381. $appeal->appeal_handled_at = now();
  382. $appeal->save();
  383. StatusService::del($status->id, true);
  384. Cache::forget('admin-dash:reports:ai-count');
  385. return redirect('/i/admin/reports/appeals');
  386. }
  387. public function updateReport(Request $request, $id)
  388. {
  389. $this->validate($request, [
  390. 'action' => 'required|string',
  391. ]);
  392. $action = $request->input('action');
  393. $actions = [
  394. 'ignore',
  395. 'cw',
  396. 'unlist',
  397. 'delete',
  398. 'shadowban',
  399. 'ban',
  400. ];
  401. if (! in_array($action, $actions)) {
  402. return abort(403);
  403. }
  404. $report = Report::findOrFail($id);
  405. $this->handleReportAction($report, $action);
  406. Cache::forget('admin-dash:reports:list-cache');
  407. return response()->json(['msg' => 'Success']);
  408. }
  409. public function handleReportAction(Report $report, $action)
  410. {
  411. $item = $report->reported();
  412. $report->admin_seen = Carbon::now();
  413. switch ($action) {
  414. case 'ignore':
  415. $report->not_interested = true;
  416. break;
  417. case 'cw':
  418. Cache::forget('status:thumb:'.$item->id);
  419. $item->is_nsfw = true;
  420. $item->save();
  421. $report->nsfw = true;
  422. StatusService::del($item->id, true);
  423. break;
  424. case 'unlist':
  425. $item->visibility = 'unlisted';
  426. $item->save();
  427. Cache::forget('profiles:private');
  428. StatusService::del($item->id, true);
  429. break;
  430. case 'delete':
  431. // Todo: fire delete job
  432. $report->admin_seen = null;
  433. StatusService::del($item->id, true);
  434. break;
  435. case 'shadowban':
  436. // Todo: fire delete job
  437. $report->admin_seen = null;
  438. break;
  439. case 'ban':
  440. // Todo: fire delete job
  441. $report->admin_seen = null;
  442. break;
  443. default:
  444. $report->admin_seen = null;
  445. break;
  446. }
  447. $report->save();
  448. return $this;
  449. }
  450. protected function actionMap()
  451. {
  452. return [
  453. '1' => 'ignore',
  454. '2' => 'cw',
  455. '3' => 'unlist',
  456. '4' => 'delete',
  457. '5' => 'shadowban',
  458. '6' => 'ban',
  459. ];
  460. }
  461. public function bulkUpdateReport(Request $request)
  462. {
  463. $this->validate($request, [
  464. 'action' => 'required|integer|min:1|max:10',
  465. 'ids' => 'required|array',
  466. ]);
  467. $action = $this->actionMap()[$request->input('action')];
  468. $ids = $request->input('ids');
  469. $reports = Report::whereIn('id', $ids)->whereNull('admin_seen')->get();
  470. foreach ($reports as $report) {
  471. $this->handleReportAction($report, $action);
  472. }
  473. $res = [
  474. 'message' => 'Success',
  475. 'code' => 200,
  476. ];
  477. return response()->json($res);
  478. }
  479. public function reportMailVerifications(Request $request)
  480. {
  481. $ids = Redis::smembers('email:manual');
  482. $ignored = Redis::smembers('email:manual-ignored');
  483. $reports = [];
  484. if ($ids) {
  485. $reports = collect($ids)
  486. ->filter(function ($id) use ($ignored) {
  487. return ! in_array($id, $ignored);
  488. })
  489. ->map(function ($id) {
  490. $user = User::whereProfileId($id)->first();
  491. if (! $user || $user->email_verified_at) {
  492. return [];
  493. }
  494. $account = AccountService::get($id, true);
  495. if (! $account) {
  496. return [];
  497. }
  498. $account['email'] = $user->email;
  499. return $account;
  500. })
  501. ->filter(function ($res) {
  502. return $res && isset($res['id']);
  503. })
  504. ->values();
  505. }
  506. return view('admin.reports.mail_verification', compact('reports', 'ignored'));
  507. }
  508. public function reportMailVerifyIgnore(Request $request)
  509. {
  510. $id = $request->input('id');
  511. Redis::sadd('email:manual-ignored', $id);
  512. return redirect('/i/admin/reports');
  513. }
  514. public function reportMailVerifyApprove(Request $request)
  515. {
  516. $id = $request->input('id');
  517. $user = User::whereProfileId($id)->firstOrFail();
  518. Redis::srem('email:manual', $id);
  519. Redis::srem('email:manual-ignored', $id);
  520. $user->email_verified_at = now();
  521. $user->save();
  522. return redirect('/i/admin/reports');
  523. }
  524. public function reportMailVerifyClearIgnored(Request $request)
  525. {
  526. Redis::del('email:manual-ignored');
  527. return [200];
  528. }
  529. public function reportsStats(Request $request)
  530. {
  531. $stats = [
  532. 'total' => Report::count(),
  533. 'open' => Report::whereNull('admin_seen')->count(),
  534. 'closed' => Report::whereNotNull('admin_seen')->count(),
  535. 'autospam' => AccountInterstitial::whereType('post.autospam')->count(),
  536. 'autospam_open' => AccountInterstitial::whereType('post.autospam')->whereNull(['appeal_handled_at'])->count(),
  537. 'appeals' => AccountInterstitial::whereNotNull('appeal_requested_at')->whereNull('appeal_handled_at')->count(),
  538. 'remote_open' => RemoteReport::whereNull('action_taken_at')->count(),
  539. 'email_verification_requests' => Redis::scard('email:manual'),
  540. ];
  541. return $stats;
  542. }
  543. public function reportsApiAll(Request $request)
  544. {
  545. $filter = $request->input('filter') == 'closed' ? 'closed' : 'open';
  546. $reports = AdminReport::collection(
  547. Report::orderBy('id', 'desc')
  548. ->when($filter, function ($q, $filter) {
  549. return $filter == 'open' ?
  550. $q->whereNull('admin_seen') :
  551. $q->whereNotNull('admin_seen');
  552. })
  553. ->groupBy(['id', 'object_id', 'object_type', 'profile_id'])
  554. ->cursorPaginate(6)
  555. ->withQueryString()
  556. );
  557. return $reports;
  558. }
  559. public function reportsApiRemote(Request $request)
  560. {
  561. $filter = $request->input('filter') == 'closed' ? 'closed' : 'open';
  562. $reports = AdminRemoteReport::collection(
  563. RemoteReport::orderBy('id', 'desc')
  564. ->when($filter, function ($q, $filter) {
  565. return $filter == 'open' ?
  566. $q->whereNull('action_taken_at') :
  567. $q->whereNotNull('action_taken_at');
  568. })
  569. ->cursorPaginate(6)
  570. ->withQueryString()
  571. );
  572. return $reports;
  573. }
  574. public function reportsApiGet(Request $request, $id)
  575. {
  576. $report = Report::findOrFail($id);
  577. return new AdminReport($report);
  578. }
  579. public function reportsApiHandle(Request $request)
  580. {
  581. $this->validate($request, [
  582. 'object_id' => 'required',
  583. 'object_type' => 'required',
  584. 'id' => 'required',
  585. 'action' => 'required|in:ignore,nsfw,unlist,private,delete,delete-all',
  586. 'action_type' => 'required|in:post,profile,story',
  587. ]);
  588. $report = Report::whereObjectId($request->input('object_id'))->findOrFail($request->input('id'));
  589. if ($request->input('action_type') === 'profile') {
  590. return $this->reportsHandleProfileAction($report, $request->input('action'));
  591. } elseif ($request->input('action_type') === 'post') {
  592. return $this->reportsHandleStatusAction($report, $request->input('action'));
  593. } elseif ($request->input('action_type') === 'story') {
  594. return $this->reportsHandleStoryAction($report, $request->input('action'));
  595. }
  596. return $report;
  597. }
  598. protected function reportsHandleStoryAction($report, $action)
  599. {
  600. switch ($action) {
  601. case 'ignore':
  602. Report::whereObjectId($report->object_id)
  603. ->whereObjectType($report->object_type)
  604. ->update([
  605. 'admin_seen' => now(),
  606. ]);
  607. return [200];
  608. break;
  609. case 'delete':
  610. $profile = Profile::find($report->reported_profile_id);
  611. $story = Story::whereProfileId($profile->id)->find($report->object_id);
  612. abort_if(! $story, 400, 'Invalid or missing story');
  613. $story->active = false;
  614. $story->save();
  615. ModLogService::boot()
  616. ->objectUid($profile->id)
  617. ->objectId($report->object_id)
  618. ->objectType('App\Story::class')
  619. ->user(request()->user())
  620. ->action('admin.user.moderate')
  621. ->metadata([
  622. 'action' => 'delete',
  623. 'message' => 'Success!',
  624. ])
  625. ->accessLevel('admin')
  626. ->save();
  627. Report::whereObjectId($report->object_id)
  628. ->whereObjectType($report->object_type)
  629. ->update([
  630. 'admin_seen' => now(),
  631. ]);
  632. StoryDelete::dispatch($story)->onQueue('story');
  633. return [200];
  634. break;
  635. case 'delete-all':
  636. $profile = Profile::find($report->reported_profile_id);
  637. $stories = Story::whereProfileId($profile->id)->whereActive(true)->get();
  638. abort_if(! $stories || ! $stories->count(), 400, 'Invalid or missing stories');
  639. ModLogService::boot()
  640. ->objectUid($profile->id)
  641. ->objectId($report->object_id)
  642. ->objectType('App\Story::class')
  643. ->user(request()->user())
  644. ->action('admin.user.moderate')
  645. ->metadata([
  646. 'action' => 'delete-all',
  647. 'message' => 'Success!',
  648. ])
  649. ->accessLevel('admin')
  650. ->save();
  651. Report::where('reported_profile_id', $profile->id)
  652. ->whereObjectType('App\Story')
  653. ->whereNull('admin_seen')
  654. ->update([
  655. 'admin_seen' => now(),
  656. ]);
  657. $stories->each(function ($story) {
  658. StoryDelete::dispatch($story)->onQueue('story');
  659. });
  660. return [200];
  661. break;
  662. }
  663. }
  664. protected function reportsHandleProfileAction($report, $action)
  665. {
  666. switch ($action) {
  667. case 'ignore':
  668. Report::whereObjectId($report->object_id)
  669. ->whereObjectType($report->object_type)
  670. ->update([
  671. 'admin_seen' => now(),
  672. ]);
  673. return [200];
  674. break;
  675. case 'nsfw':
  676. if ($report->object_type === 'App\Profile') {
  677. $profile = Profile::find($report->object_id);
  678. } elseif ($report->object_type === 'App\Status') {
  679. $status = Status::find($report->object_id);
  680. if (! $status) {
  681. return [200];
  682. }
  683. $profile = Profile::find($status->profile_id);
  684. }
  685. if (! $profile) {
  686. return;
  687. }
  688. abort_if($profile->user && $profile->user->is_admin, 400, 'Cannot moderate an admin account.');
  689. $profile->cw = true;
  690. $profile->save();
  691. foreach (Status::whereProfileId($profile->id)->cursor() as $status) {
  692. $status->is_nsfw = true;
  693. $status->save();
  694. StatusService::del($status->id);
  695. PublicTimelineService::rem($status->id);
  696. }
  697. ModLogService::boot()
  698. ->objectUid($profile->id)
  699. ->objectId($profile->id)
  700. ->objectType('App\Profile::class')
  701. ->user(request()->user())
  702. ->action('admin.user.moderate')
  703. ->metadata([
  704. 'action' => 'cw',
  705. 'message' => 'Success!',
  706. ])
  707. ->accessLevel('admin')
  708. ->save();
  709. Report::whereObjectId($report->object_id)
  710. ->whereObjectType($report->object_type)
  711. ->update([
  712. 'nsfw' => true,
  713. 'admin_seen' => now(),
  714. ]);
  715. return [200];
  716. break;
  717. case 'unlist':
  718. if ($report->object_type === 'App\Profile') {
  719. $profile = Profile::find($report->object_id);
  720. } elseif ($report->object_type === 'App\Status') {
  721. $status = Status::find($report->object_id);
  722. if (! $status) {
  723. return [200];
  724. }
  725. $profile = Profile::find($status->profile_id);
  726. }
  727. if (! $profile) {
  728. return;
  729. }
  730. abort_if($profile->user && $profile->user->is_admin, 400, 'Cannot moderate an admin account.');
  731. $profile->unlisted = true;
  732. $profile->save();
  733. foreach (Status::whereProfileId($profile->id)->whereScope('public')->cursor() as $status) {
  734. $status->scope = 'unlisted';
  735. $status->visibility = 'unlisted';
  736. $status->save();
  737. StatusService::del($status->id);
  738. PublicTimelineService::rem($status->id);
  739. }
  740. ModLogService::boot()
  741. ->objectUid($profile->id)
  742. ->objectId($profile->id)
  743. ->objectType('App\Profile::class')
  744. ->user(request()->user())
  745. ->action('admin.user.moderate')
  746. ->metadata([
  747. 'action' => 'unlisted',
  748. 'message' => 'Success!',
  749. ])
  750. ->accessLevel('admin')
  751. ->save();
  752. Report::whereObjectId($report->object_id)
  753. ->whereObjectType($report->object_type)
  754. ->update([
  755. 'admin_seen' => now(),
  756. ]);
  757. return [200];
  758. break;
  759. case 'private':
  760. if ($report->object_type === 'App\Profile') {
  761. $profile = Profile::find($report->object_id);
  762. } elseif ($report->object_type === 'App\Status') {
  763. $status = Status::find($report->object_id);
  764. if (! $status) {
  765. return [200];
  766. }
  767. $profile = Profile::find($status->profile_id);
  768. }
  769. if (! $profile) {
  770. return;
  771. }
  772. abort_if($profile->user && $profile->user->is_admin, 400, 'Cannot moderate an admin account.');
  773. $profile->unlisted = true;
  774. $profile->save();
  775. foreach (Status::whereProfileId($profile->id)->cursor() as $status) {
  776. $status->scope = 'private';
  777. $status->visibility = 'private';
  778. $status->save();
  779. StatusService::del($status->id);
  780. PublicTimelineService::rem($status->id);
  781. }
  782. ModLogService::boot()
  783. ->objectUid($profile->id)
  784. ->objectId($profile->id)
  785. ->objectType('App\Profile::class')
  786. ->user(request()->user())
  787. ->action('admin.user.moderate')
  788. ->metadata([
  789. 'action' => 'private',
  790. 'message' => 'Success!',
  791. ])
  792. ->accessLevel('admin')
  793. ->save();
  794. Report::whereObjectId($report->object_id)
  795. ->whereObjectType($report->object_type)
  796. ->update([
  797. 'admin_seen' => now(),
  798. ]);
  799. return [200];
  800. break;
  801. case 'delete':
  802. if (config('pixelfed.account_deletion') == false) {
  803. abort(404);
  804. }
  805. if ($report->object_type === 'App\Profile') {
  806. $profile = Profile::find($report->object_id);
  807. } elseif ($report->object_type === 'App\Status') {
  808. $status = Status::find($report->object_id);
  809. if (! $status) {
  810. return [200];
  811. }
  812. $profile = Profile::find($status->profile_id);
  813. }
  814. if (! $profile) {
  815. return;
  816. }
  817. abort_if($profile->user && $profile->user->is_admin, 400, 'Cannot delete an admin account.');
  818. $ts = now()->addMonth();
  819. if ($profile->user_id) {
  820. $user = $profile->user;
  821. abort_if($user->is_admin, 403, 'You cannot delete admin accounts.');
  822. $user->status = 'delete';
  823. $user->delete_after = $ts;
  824. $user->save();
  825. }
  826. $profile->status = 'delete';
  827. $profile->delete_after = $ts;
  828. $profile->save();
  829. ModLogService::boot()
  830. ->objectUid($profile->id)
  831. ->objectId($profile->id)
  832. ->objectType('App\Profile::class')
  833. ->user(request()->user())
  834. ->action('admin.user.delete')
  835. ->accessLevel('admin')
  836. ->save();
  837. Report::whereObjectId($report->object_id)
  838. ->whereObjectType($report->object_type)
  839. ->update([
  840. 'admin_seen' => now(),
  841. ]);
  842. if ($profile->user_id) {
  843. DB::table('oauth_access_tokens')->whereUserId($user->id)->delete();
  844. DB::table('oauth_auth_codes')->whereUserId($user->id)->delete();
  845. $user->email = $user->id;
  846. $user->password = '';
  847. $user->status = 'delete';
  848. $user->save();
  849. $profile->status = 'delete';
  850. $profile->delete_after = now()->addMonth();
  851. $profile->save();
  852. AccountService::del($profile->id);
  853. DeleteAccountPipeline::dispatch($user)->onQueue('high');
  854. } else {
  855. $profile->status = 'delete';
  856. $profile->delete_after = now()->addMonth();
  857. $profile->save();
  858. AccountService::del($profile->id);
  859. DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('high');
  860. }
  861. return [200];
  862. break;
  863. }
  864. }
  865. protected function reportsHandleStatusAction($report, $action)
  866. {
  867. switch ($action) {
  868. case 'ignore':
  869. Report::whereObjectId($report->object_id)
  870. ->whereObjectType($report->object_type)
  871. ->update([
  872. 'admin_seen' => now(),
  873. ]);
  874. return [200];
  875. break;
  876. case 'nsfw':
  877. $status = Status::find($report->object_id);
  878. if (! $status) {
  879. return [200];
  880. }
  881. abort_if($status->profile->user && $status->profile->user->is_admin, 400, 'Cannot moderate an admin account post.');
  882. $status->is_nsfw = true;
  883. $status->save();
  884. StatusService::del($status->id);
  885. ModLogService::boot()
  886. ->objectUid($status->profile_id)
  887. ->objectId($status->profile_id)
  888. ->objectType('App\Status::class')
  889. ->user(request()->user())
  890. ->action('admin.status.moderate')
  891. ->metadata([
  892. 'action' => 'cw',
  893. 'message' => 'Success!',
  894. ])
  895. ->accessLevel('admin')
  896. ->save();
  897. Report::whereObjectId($report->object_id)
  898. ->whereObjectType($report->object_type)
  899. ->update([
  900. 'nsfw' => true,
  901. 'admin_seen' => now(),
  902. ]);
  903. return [200];
  904. break;
  905. case 'private':
  906. $status = Status::find($report->object_id);
  907. if (! $status) {
  908. return [200];
  909. }
  910. abort_if($status->profile->user && $status->profile->user->is_admin, 400, 'Cannot moderate an admin account post.');
  911. $status->scope = 'private';
  912. $status->visibility = 'private';
  913. $status->save();
  914. StatusService::del($status->id);
  915. PublicTimelineService::rem($status->id);
  916. ModLogService::boot()
  917. ->objectUid($status->profile_id)
  918. ->objectId($status->profile_id)
  919. ->objectType('App\Status::class')
  920. ->user(request()->user())
  921. ->action('admin.status.moderate')
  922. ->metadata([
  923. 'action' => 'private',
  924. 'message' => 'Success!',
  925. ])
  926. ->accessLevel('admin')
  927. ->save();
  928. Report::whereObjectId($report->object_id)
  929. ->whereObjectType($report->object_type)
  930. ->update([
  931. 'admin_seen' => now(),
  932. ]);
  933. return [200];
  934. break;
  935. case 'unlist':
  936. $status = Status::find($report->object_id);
  937. if (! $status) {
  938. return [200];
  939. }
  940. abort_if($status->profile->user && $status->profile->user->is_admin, 400, 'Cannot moderate an admin account post.');
  941. if ($status->scope === 'public') {
  942. $status->scope = 'unlisted';
  943. $status->visibility = 'unlisted';
  944. $status->save();
  945. StatusService::del($status->id);
  946. PublicTimelineService::rem($status->id);
  947. }
  948. ModLogService::boot()
  949. ->objectUid($status->profile_id)
  950. ->objectId($status->profile_id)
  951. ->objectType('App\Status::class')
  952. ->user(request()->user())
  953. ->action('admin.status.moderate')
  954. ->metadata([
  955. 'action' => 'unlist',
  956. 'message' => 'Success!',
  957. ])
  958. ->accessLevel('admin')
  959. ->save();
  960. Report::whereObjectId($report->object_id)
  961. ->whereObjectType($report->object_type)
  962. ->update([
  963. 'admin_seen' => now(),
  964. ]);
  965. return [200];
  966. break;
  967. case 'delete':
  968. $status = Status::find($report->object_id);
  969. if (! $status) {
  970. return [200];
  971. }
  972. $profile = $status->profile;
  973. abort_if($profile->user && $profile->user->is_admin, 400, 'Cannot delete an admin account post.');
  974. StatusService::del($status->id);
  975. if ($profile->user_id != null && $profile->domain == null) {
  976. PublicTimelineService::del($status->id);
  977. StatusDelete::dispatch($status)->onQueue('high');
  978. } else {
  979. NetworkTimelineService::del($status->id);
  980. RemoteStatusDelete::dispatch($status)->onQueue('high');
  981. }
  982. Report::whereObjectId($report->object_id)
  983. ->whereObjectType($report->object_type)
  984. ->update([
  985. 'admin_seen' => now(),
  986. ]);
  987. return [200];
  988. break;
  989. }
  990. }
  991. public function reportsApiSpamAll(Request $request)
  992. {
  993. $tab = $request->input('tab', 'home');
  994. $appeals = AdminSpamReport::collection(
  995. AccountInterstitial::orderBy('id', 'desc')
  996. ->whereType('post.autospam')
  997. ->whereNull('appeal_handled_at')
  998. ->cursorPaginate(6)
  999. ->withQueryString()
  1000. );
  1001. return $appeals;
  1002. }
  1003. public function reportsApiSpamHandle(Request $request)
  1004. {
  1005. $this->validate($request, [
  1006. 'id' => 'required',
  1007. 'action' => 'required|in:mark-read,mark-not-spam,mark-all-read,mark-all-not-spam,delete-profile',
  1008. ]);
  1009. $action = $request->input('action');
  1010. abort_if(
  1011. $action === 'delete-profile' &&
  1012. ! config('pixelfed.account_deletion'),
  1013. 404,
  1014. "Cannot delete profile, account_deletion is disabled.\n\n Set `ACCOUNT_DELETION=true` in .env and re-cache config."
  1015. );
  1016. $report = AccountInterstitial::with('user')
  1017. ->whereType('post.autospam')
  1018. ->whereNull('appeal_handled_at')
  1019. ->findOrFail($request->input('id'));
  1020. $this->reportsHandleSpamAction($report, $action);
  1021. Cache::forget('admin-dash:reports:spam-count');
  1022. Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$report->user->profile_id);
  1023. Cache::forget('pf:bouncer_v0:recent_by_pid:'.$report->user->profile_id);
  1024. return [$action, $report];
  1025. }
  1026. public function reportsHandleSpamAction($appeal, $action)
  1027. {
  1028. $meta = json_decode($appeal->meta);
  1029. if ($action == 'mark-read') {
  1030. $appeal->is_spam = true;
  1031. $appeal->appeal_handled_at = now();
  1032. $appeal->save();
  1033. PublicTimelineService::del($appeal->item_id);
  1034. }
  1035. if ($action == 'mark-not-spam') {
  1036. $status = $appeal->status;
  1037. $status->is_nsfw = $meta->is_nsfw;
  1038. $status->scope = 'public';
  1039. $status->visibility = 'public';
  1040. $status->save();
  1041. $appeal->is_spam = false;
  1042. $appeal->appeal_handled_at = now();
  1043. $appeal->save();
  1044. Notification::whereAction('autospam.warning')
  1045. ->whereProfileId($appeal->user->profile_id)
  1046. ->get()
  1047. ->each(function ($n) use ($appeal) {
  1048. NotificationService::del($appeal->user->profile_id, $n->id);
  1049. $n->forceDelete();
  1050. });
  1051. StatusService::del($status->id);
  1052. StatusService::get($status->id);
  1053. if ($status->in_reply_to_id == null && $status->reblog_of_id == null) {
  1054. PublicTimelineService::add($status->id);
  1055. }
  1056. }
  1057. if ($action == 'mark-all-read') {
  1058. AccountInterstitial::whereType('post.autospam')
  1059. ->whereItemType('App\Status')
  1060. ->whereNull('appeal_handled_at')
  1061. ->whereUserId($appeal->user_id)
  1062. ->update([
  1063. 'appeal_handled_at' => now(),
  1064. 'is_spam' => true,
  1065. ]);
  1066. }
  1067. if ($action == 'mark-all-not-spam') {
  1068. AccountInterstitial::whereType('post.autospam')
  1069. ->whereItemType('App\Status')
  1070. ->whereUserId($appeal->user_id)
  1071. ->get()
  1072. ->each(function ($report) use ($meta) {
  1073. $report->is_spam = false;
  1074. $report->appeal_handled_at = now();
  1075. $report->save();
  1076. $status = Status::find($report->item_id);
  1077. if ($status) {
  1078. $status->is_nsfw = $meta->is_nsfw;
  1079. $status->scope = 'public';
  1080. $status->visibility = 'public';
  1081. $status->save();
  1082. StatusService::del($status->id);
  1083. }
  1084. Notification::whereAction('autospam.warning')
  1085. ->whereProfileId($report->user->profile_id)
  1086. ->get()
  1087. ->each(function ($n) use ($report) {
  1088. NotificationService::del($report->user->profile_id, $n->id);
  1089. $n->forceDelete();
  1090. });
  1091. });
  1092. }
  1093. if ($action == 'delete-profile') {
  1094. $user = User::findOrFail($appeal->user_id);
  1095. $profile = $user->profile;
  1096. if ($user->is_admin == true) {
  1097. $mid = request()->user()->id;
  1098. abort_if($user->id < $mid, 403, 'You cannot delete an admin account.');
  1099. }
  1100. $ts = now()->addMonth();
  1101. $user->status = 'delete';
  1102. $profile->status = 'delete';
  1103. $user->delete_after = $ts;
  1104. $profile->delete_after = $ts;
  1105. $user->save();
  1106. $profile->save();
  1107. $appeal->appeal_handled_at = now();
  1108. $appeal->save();
  1109. ModLogService::boot()
  1110. ->objectUid($user->id)
  1111. ->objectId($user->id)
  1112. ->objectType('App\User::class')
  1113. ->user(request()->user())
  1114. ->action('admin.user.delete')
  1115. ->accessLevel('admin')
  1116. ->save();
  1117. Cache::forget('profiles:private');
  1118. DeleteAccountPipeline::dispatch($user);
  1119. }
  1120. }
  1121. public function reportsApiSpamGet(Request $request, $id)
  1122. {
  1123. $report = AccountInterstitial::findOrFail($id);
  1124. return new AdminSpamReport($report);
  1125. }
  1126. public function reportsApiRemoteHandle(Request $request)
  1127. {
  1128. $this->validate($request, [
  1129. 'id' => 'required|exists:remote_reports,id',
  1130. 'action' => 'required|in:mark-read,cw-posts,unlist-posts,delete-posts,private-posts,mark-all-read-by-domain,mark-all-read-by-username,cw-all-posts,private-all-posts,unlist-all-posts'
  1131. ]);
  1132. $report = RemoteReport::findOrFail($request->input('id'));
  1133. $user = User::whereProfileId($report->account_id)->first();
  1134. $ogPublicStatuses = [];
  1135. $ogUnlistedStatuses = [];
  1136. $ogNonCwStatuses = [];
  1137. switch ($request->input('action')) {
  1138. case 'mark-read':
  1139. $report->action_taken_at = now();
  1140. $report->save();
  1141. break;
  1142. case 'mark-all-read-by-domain':
  1143. RemoteReport::whereInstanceId($report->instance_id)->update(['action_taken_at' => now()]);
  1144. break;
  1145. case 'cw-posts':
  1146. $statuses = Status::find($report->status_ids);
  1147. foreach($statuses as $status) {
  1148. if($report->account_id != $status->profile_id) {
  1149. continue;
  1150. }
  1151. if(!$status->is_nsfw) {
  1152. $ogNonCwStatuses[] = $status->id;
  1153. }
  1154. $status->is_nsfw = true;
  1155. $status->saveQuietly();
  1156. StatusService::del($status->id);
  1157. }
  1158. $report->action_taken_at = now();
  1159. $report->save();
  1160. break;
  1161. case 'cw-all-posts':
  1162. foreach(Status::whereProfileId($report->account_id)->lazyById(50, 'id') as $status) {
  1163. if($status->is_nsfw || $status->reblog_of_id) {
  1164. continue;
  1165. }
  1166. if(!$status->is_nsfw) {
  1167. $ogNonCwStatuses[] = $status->id;
  1168. }
  1169. $status->is_nsfw = true;
  1170. $status->saveQuietly();
  1171. StatusService::del($status->id);
  1172. }
  1173. break;
  1174. case 'unlist-posts':
  1175. $statuses = Status::find($report->status_ids);
  1176. foreach($statuses as $status) {
  1177. if($report->account_id != $status->profile_id) {
  1178. continue;
  1179. }
  1180. if($status->scope === 'public') {
  1181. $ogPublicStatuses[] = $status->id;
  1182. $status->scope = 'unlisted';
  1183. $status->visibility = 'unlisted';
  1184. $status->saveQuietly();
  1185. StatusService::del($status->id);
  1186. }
  1187. }
  1188. $report->action_taken_at = now();
  1189. $report->save();
  1190. break;
  1191. case 'unlist-all-posts':
  1192. foreach(Status::whereProfileId($report->account_id)->lazyById(50, 'id') as $status) {
  1193. if($status->visibility !== 'public' || $status->reblog_of_id) {
  1194. continue;
  1195. }
  1196. $ogPublicStatuses[] = $status->id;
  1197. $status->visibility = 'unlisted';
  1198. $status->scope = 'unlisted';
  1199. $status->saveQuietly();
  1200. StatusService::del($status->id);
  1201. }
  1202. break;
  1203. case 'private-posts':
  1204. $statuses = Status::find($report->status_ids);
  1205. foreach($statuses as $status) {
  1206. if($report->account_id != $status->profile_id) {
  1207. continue;
  1208. }
  1209. if(in_array($status->scope, ['public', 'unlisted', 'private'])) {
  1210. if($status->scope === 'public') {
  1211. $ogPublicStatuses[] = $status->id;
  1212. }
  1213. $status->scope = 'private';
  1214. $status->visibility = 'private';
  1215. $status->saveQuietly();
  1216. StatusService::del($status->id);
  1217. }
  1218. }
  1219. $report->action_taken_at = now();
  1220. $report->save();
  1221. break;
  1222. case 'private-all-posts':
  1223. foreach(Status::whereProfileId($report->account_id)->lazyById(50, 'id') as $status) {
  1224. if(!in_array($status->visibility, ['public', 'unlisted']) || $status->reblog_of_id) {
  1225. continue;
  1226. }
  1227. if($status->visibility === 'public') {
  1228. $ogPublicStatuses[] = $status->id;
  1229. } else if($status->visibility === 'unlisted') {
  1230. $ogUnlistedStatuses[] = $status->id;
  1231. }
  1232. $status->visibility = 'private';
  1233. $status->scope = 'private';
  1234. $status->saveQuietly();
  1235. StatusService::del($status->id);
  1236. }
  1237. break;
  1238. case 'delete-posts':
  1239. $statuses = Status::find($report->status_ids);
  1240. foreach($statuses as $status) {
  1241. if($report->account_id != $status->profile_id) {
  1242. continue;
  1243. }
  1244. StatusDelete::dispatch($status);
  1245. }
  1246. $report->action_taken_at = now();
  1247. $report->save();
  1248. break;
  1249. case 'mark-all-read-by-username':
  1250. RemoteReport::whereNull('action_taken_at')->whereAccountId($report->account_id)->update(['action_taken_at' => now()]);
  1251. break;
  1252. default:
  1253. abort(404);
  1254. break;
  1255. }
  1256. if($ogPublicStatuses && count($ogPublicStatuses)) {
  1257. Storage::disk('local')->put('mod-log-cache/' . $report->account_id . '/' . now()->format('Y-m-d') . '-og-public-statuses.json', json_encode($ogPublicStatuses, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES));
  1258. }
  1259. if($ogNonCwStatuses && count($ogNonCwStatuses)) {
  1260. Storage::disk('local')->put('mod-log-cache/' . $report->account_id . '/' . now()->format('Y-m-d') . '-og-noncw-statuses.json', json_encode($ogNonCwStatuses, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES));
  1261. }
  1262. if($ogUnlistedStatuses && count($ogUnlistedStatuses)) {
  1263. Storage::disk('local')->put('mod-log-cache/' . $report->account_id . '/' . now()->format('Y-m-d') . '-og-unlisted-statuses.json', json_encode($ogUnlistedStatuses, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES));
  1264. }
  1265. ModLogService::boot()
  1266. ->user(request()->user())
  1267. ->objectUid($user ? $user->id : null)
  1268. ->objectId($report->id)
  1269. ->objectType('App\Report::class')
  1270. ->action('admin.report.moderate')
  1271. ->metadata([
  1272. 'action' => $request->input('action'),
  1273. 'duration_active' => now()->parse($report->created_at)->diffForHumans()
  1274. ])
  1275. ->accessLevel('admin')
  1276. ->save();
  1277. if($report->status_ids) {
  1278. foreach($report->status_ids as $sid) {
  1279. RemoteReport::whereNull('action_taken_at')
  1280. ->whereJsonContains('status_ids', [$sid])
  1281. ->update(['action_taken_at' => now()]);
  1282. }
  1283. }
  1284. return [200];
  1285. }
  1286. }