AdminReportController.php 61 KB

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