2018-08-19 22:35:16 +00:00
< ? php
namespace App\Http\Controllers\Admin ;
2018-12-05 03:11:50 +00:00
use Cache ;
2018-08-19 22:35:16 +00:00
use Carbon\Carbon ;
2018-08-28 03:07:36 +00:00
use Illuminate\Http\Request ;
2021-11-09 06:02:34 +00:00
use Illuminate\Support\Facades\Redis ;
use App\Services\AccountService ;
use App\Services\StatusService ;
2021-11-09 07:06:56 +00:00
use App\ {
AccountInterstitial ,
Contact ,
Hashtag ,
Newsroom ,
OauthClient ,
Profile ,
Report ,
Status ,
Story ,
User
};
use Illuminate\Validation\Rule ;
use App\Services\StoryService ;
2022-04-18 06:30:06 +00:00
use App\Services\ModLogService ;
use App\Jobs\DeletePipeline\DeleteAccountPipeline ;
2018-08-19 22:35:16 +00:00
trait AdminReportController
{
2021-11-09 07:06:56 +00:00
public function reports ( Request $request )
{
$filter = $request -> input ( 'filter' ) == 'closed' ? 'closed' : 'open' ;
$page = $request -> input ( 'page' ) ? ? 1 ;
$ai = Cache :: remember ( 'admin-dash:reports:ai-count' , 3600 , function () {
return AccountInterstitial :: whereNotNull ( 'appeal_requested_at' ) -> whereNull ( 'appeal_handled_at' ) -> count ();
});
$spam = Cache :: remember ( 'admin-dash:reports:spam-count' , 3600 , function () {
return AccountInterstitial :: whereType ( 'post.autospam' ) -> whereNull ( 'appeal_handled_at' ) -> count ();
});
$mailVerifications = Redis :: scard ( 'email:manual' );
if ( $filter == 'open' && $page == 1 ) {
$reports = Cache :: remember ( 'admin-dash:reports:list-cache' , 300 , function () use ( $page , $filter ) {
return Report :: whereHas ( 'status' )
-> whereHas ( 'reportedUser' )
-> whereHas ( 'reporter' )
-> orderBy ( 'created_at' , 'desc' )
-> when ( $filter , function ( $q , $filter ) {
return $filter == 'open' ?
$q -> whereNull ( 'admin_seen' ) :
$q -> whereNotNull ( 'admin_seen' );
})
-> paginate ( 6 );
});
} else {
$reports = Report :: whereHas ( 'status' )
-> whereHas ( 'reportedUser' )
-> whereHas ( 'reporter' )
-> orderBy ( 'created_at' , 'desc' )
-> when ( $filter , function ( $q , $filter ) {
return $filter == 'open' ?
$q -> whereNull ( 'admin_seen' ) :
$q -> whereNotNull ( 'admin_seen' );
})
-> paginate ( 6 );
}
return view ( 'admin.reports.home' , compact ( 'reports' , 'ai' , 'spam' , 'mailVerifications' ));
}
public function showReport ( Request $request , $id )
{
2022-07-26 09:11:28 +00:00
$report = Report :: with ( 'status' ) -> findOrFail ( $id );
2021-11-09 07:06:56 +00:00
return view ( 'admin.reports.show' , compact ( 'report' ));
}
public function appeals ( Request $request )
{
$appeals = AccountInterstitial :: whereNotNull ( 'appeal_requested_at' )
-> whereNull ( 'appeal_handled_at' )
-> latest ()
-> paginate ( 6 );
return view ( 'admin.reports.appeals' , compact ( 'appeals' ));
}
public function showAppeal ( Request $request , $id )
{
$appeal = AccountInterstitial :: whereNotNull ( 'appeal_requested_at' )
-> whereNull ( 'appeal_handled_at' )
-> findOrFail ( $id );
$meta = json_decode ( $appeal -> meta );
return view ( 'admin.reports.show_appeal' , compact ( 'appeal' , 'meta' ));
}
public function spam ( Request $request )
{
2021-11-11 04:46:31 +00:00
$this -> validate ( $request , [
'tab' => 'sometimes|in:home,not-spam,spam,settings,custom,exemptions'
]);
$tab = $request -> input ( 'tab' , 'home' );
$openCount = Cache :: remember ( 'admin-dash:reports:spam-count' , 3600 , function () {
return AccountInterstitial :: whereType ( 'post.autospam' )
-> whereNull ( 'appeal_handled_at' )
-> count ();
});
$monthlyCount = Cache :: remember ( 'admin-dash:reports:spam-count:30d' , 43200 , function () {
return AccountInterstitial :: whereType ( 'post.autospam' )
-> where ( 'created_at' , '>' , now () -> subMonth ())
-> count ();
});
$totalCount = Cache :: remember ( 'admin-dash:reports:spam-count:total' , 43200 , function () {
return AccountInterstitial :: whereType ( 'post.autospam' ) -> count ();
});
$uncategorized = Cache :: remember ( 'admin-dash:reports:spam-sync' , 3600 , function () {
return AccountInterstitial :: whereType ( 'post.autospam' )
-> whereIsSpam ( null )
-> whereNotNull ( 'appeal_handled_at' )
-> exists ();
});
$avg = Cache :: remember ( 'admin-dash:reports:spam-count:avg' , 43200 , function () {
if ( config ( 'database.default' ) != 'mysql' ) {
return 0 ;
}
return AccountInterstitial :: selectRaw ( '*, count(id) as counter' )
-> whereType ( 'post.autospam' )
-> groupBy ( 'user_id' )
-> get ()
-> avg ( 'counter' );
});
$avgOpen = Cache :: remember ( 'admin-dash:reports:spam-count:avgopen' , 43200 , function () {
if ( config ( 'database.default' ) != 'mysql' ) {
return " 0 " ;
}
$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 ();
if ( ! $seconds ) {
return " 0 " ;
}
$mins = floor ( $seconds -> avg ( 'timediff' ) / 60 );
if ( $mins < 60 ) {
return $mins . ' min(s)' ;
}
if ( $mins < 2880 ) {
return floor ( $mins / 60 ) . ' hour(s)' ;
}
return floor ( $mins / 60 / 24 ) . ' day(s)' ;
});
$avgCount = $totalCount && $avg ? floor ( $totalCount / $avg ) : " 0 " ;
if ( in_array ( $tab , [ 'home' , 'spam' , 'not-spam' ])) {
$appeals = AccountInterstitial :: whereType ( 'post.autospam' )
-> when ( $tab , function ( $q , $tab ) {
switch ( $tab ) {
case 'home' :
return $q -> whereNull ( 'appeal_handled_at' );
break ;
case 'spam' :
return $q -> whereIsSpam ( true );
break ;
case 'not-spam' :
return $q -> whereIsSpam ( false );
break ;
}
})
-> latest ()
-> paginate ( 6 );
if ( $tab !== 'home' ) {
$appeals = $appeals -> appends ([ 'tab' => $tab ]);
}
} else {
$appeals = new class {
public function count () {
return 0 ;
}
public function render () {
return ;
}
};
}
return view ( 'admin.reports.spam' , compact ( 'tab' , 'appeals' , 'openCount' , 'monthlyCount' , 'totalCount' , 'avgCount' , 'avgOpen' , 'uncategorized' ));
2021-11-09 07:06:56 +00:00
}
public function showSpam ( Request $request , $id )
{
$appeal = AccountInterstitial :: whereType ( 'post.autospam' )
-> findOrFail ( $id );
$meta = json_decode ( $appeal -> meta );
return view ( 'admin.reports.show_spam' , compact ( 'appeal' , 'meta' ));
}
2021-11-11 04:46:31 +00:00
public function fixUncategorizedSpam ( Request $request )
{
if ( Cache :: get ( 'admin-dash:reports:spam-sync-active' )) {
return redirect ( '/i/admin/reports/autospam' );
}
Cache :: put ( 'admin-dash:reports:spam-sync-active' , 1 , 900 );
AccountInterstitial :: chunk ( 500 , function ( $reports ) {
foreach ( $reports as $report ) {
if ( $report -> item_type != 'App\Status' ) {
continue ;
}
if ( $report -> type != 'post.autospam' ) {
continue ;
}
if ( $report -> is_spam != null ) {
continue ;
}
$status = StatusService :: get ( $report -> item_id , false );
if ( ! $status ) {
return ;
}
$scope = $status [ 'visibility' ];
$report -> is_spam = $scope == 'unlisted' ;
$report -> in_violation = $report -> is_spam ;
$report -> severity_index = 1 ;
$report -> save ();
}
});
Cache :: forget ( 'admin-dash:reports:spam-sync' );
return redirect ( '/i/admin/reports/autospam' );
}
2021-11-09 07:06:56 +00:00
public function updateSpam ( Request $request , $id )
{
$this -> validate ( $request , [
2022-07-26 09:11:28 +00:00
'action' => 'required|in:dismiss,approve,dismiss-all,approve-all,delete-account,mark-spammer'
2021-11-09 07:06:56 +00:00
]);
$action = $request -> input ( 'action' );
$appeal = AccountInterstitial :: whereType ( 'post.autospam' )
-> whereNull ( 'appeal_handled_at' )
-> findOrFail ( $id );
$meta = json_decode ( $appeal -> meta );
2021-11-11 04:46:31 +00:00
$res = [ 'status' => 'success' ];
$now = now ();
Cache :: forget ( 'admin-dash:reports:spam-count:total' );
Cache :: forget ( 'admin-dash:reports:spam-count:30d' );
2021-11-09 07:06:56 +00:00
2022-04-18 06:30:06 +00:00
if ( $action == 'delete-account' ) {
if ( config ( 'pixelfed.account_deletion' ) == false ) {
abort ( 404 );
}
$user = User :: findOrFail ( $appeal -> user_id );
$profile = $user -> profile ;
if ( $user -> is_admin == true ) {
$mid = $request -> user () -> id ;
abort_if ( $user -> id < $mid , 403 );
}
$ts = now () -> addMonth ();
$user -> status = 'delete' ;
$profile -> status = 'delete' ;
$user -> delete_after = $ts ;
$profile -> delete_after = $ts ;
$user -> save ();
$profile -> save ();
ModLogService :: boot ()
-> objectUid ( $user -> id )
-> objectId ( $user -> id )
-> objectType ( 'App\User::class' )
-> user ( $request -> user ())
-> action ( 'admin.user.delete' )
-> accessLevel ( 'admin' )
-> save ();
Cache :: forget ( 'profiles:private' );
DeleteAccountPipeline :: dispatch ( $user ) -> onQueue ( 'high' );
return ;
}
2021-11-09 07:06:56 +00:00
if ( $action == 'dismiss' ) {
2021-11-11 04:46:31 +00:00
$appeal -> is_spam = true ;
$appeal -> appeal_handled_at = $now ;
2021-11-09 07:06:56 +00:00
$appeal -> save ();
Cache :: forget ( 'pf:bouncer_v0:exemption_by_pid:' . $appeal -> user -> profile_id );
Cache :: forget ( 'pf:bouncer_v0:recent_by_pid:' . $appeal -> user -> profile_id );
Cache :: forget ( 'admin-dash:reports:spam-count' );
2021-11-11 04:46:31 +00:00
return $res ;
}
if ( $action == 'dismiss-all' ) {
AccountInterstitial :: whereType ( 'post.autospam' )
-> whereItemType ( 'App\Status' )
-> whereNull ( 'appeal_handled_at' )
-> whereUserId ( $appeal -> user_id )
-> update ([ 'appeal_handled_at' => $now , 'is_spam' => true ]);
Cache :: forget ( 'pf:bouncer_v0:exemption_by_pid:' . $appeal -> user -> profile_id );
Cache :: forget ( 'pf:bouncer_v0:recent_by_pid:' . $appeal -> user -> profile_id );
Cache :: forget ( 'admin-dash:reports:spam-count' );
return $res ;
}
if ( $action == 'approve-all' ) {
AccountInterstitial :: whereType ( 'post.autospam' )
-> whereItemType ( 'App\Status' )
-> whereNull ( 'appeal_handled_at' )
-> whereUserId ( $appeal -> user_id )
-> get ()
-> each ( function ( $report ) use ( $meta ) {
$report -> is_spam = false ;
$report -> appeal_handled_at = now ();
$report -> save ();
$status = Status :: find ( $report -> item_id );
if ( $status ) {
$status -> is_nsfw = $meta -> is_nsfw ;
$status -> scope = 'public' ;
$status -> visibility = 'public' ;
$status -> save ();
2021-12-13 05:30:55 +00:00
StatusService :: del ( $status -> id , true );
2021-11-11 04:46:31 +00:00
}
});
Cache :: forget ( 'pf:bouncer_v0:exemption_by_pid:' . $appeal -> user -> profile_id );
2022-07-26 09:11:28 +00:00
Cache :: forget ( 'pf:bouncer_v0:recent_by_pid:' . $appeal -> user -> profile_id );
Cache :: forget ( 'admin-dash:reports:spam-count' );
return $res ;
}
if ( $action == 'mark-spammer' ) {
AccountInterstitial :: whereType ( 'post.autospam' )
-> whereItemType ( 'App\Status' )
-> whereNull ( 'appeal_handled_at' )
-> whereUserId ( $appeal -> user_id )
-> update ([ 'appeal_handled_at' => $now , 'is_spam' => true ]);
$pro = Profile :: whereUserId ( $appeal -> user_id ) -> firstOrFail ();
$pro -> update ([
'unlisted' => true ,
'cw' => true ,
'no_autolink' => true
]);
Status :: whereProfileId ( $pro -> id )
-> get ()
-> each ( function ( $report ) {
$status -> is_nsfw = $meta -> is_nsfw ;
$status -> scope = 'public' ;
$status -> visibility = 'public' ;
$status -> save ();
StatusService :: del ( $status -> id , true );
});
Cache :: forget ( 'pf:bouncer_v0:exemption_by_pid:' . $appeal -> user -> profile_id );
2021-11-11 04:46:31 +00:00
Cache :: forget ( 'pf:bouncer_v0:recent_by_pid:' . $appeal -> user -> profile_id );
Cache :: forget ( 'admin-dash:reports:spam-count' );
return $res ;
2021-11-09 07:06:56 +00:00
}
$status = $appeal -> status ;
$status -> is_nsfw = $meta -> is_nsfw ;
$status -> scope = 'public' ;
$status -> visibility = 'public' ;
$status -> save ();
2021-11-11 04:46:31 +00:00
$appeal -> is_spam = false ;
2021-11-09 07:06:56 +00:00
$appeal -> appeal_handled_at = now ();
$appeal -> save ();
StatusService :: del ( $status -> id );
Cache :: forget ( 'pf:bouncer_v0:exemption_by_pid:' . $appeal -> user -> profile_id );
Cache :: forget ( 'pf:bouncer_v0:recent_by_pid:' . $appeal -> user -> profile_id );
Cache :: forget ( 'admin-dash:reports:spam-count' );
2021-11-11 04:46:31 +00:00
return $res ;
2021-11-09 07:06:56 +00:00
}
public function updateAppeal ( Request $request , $id )
{
$this -> validate ( $request , [
'action' => 'required|in:dismiss,approve'
]);
$action = $request -> input ( 'action' );
$appeal = AccountInterstitial :: whereNotNull ( 'appeal_requested_at' )
-> whereNull ( 'appeal_handled_at' )
-> findOrFail ( $id );
if ( $action == 'dismiss' ) {
$appeal -> appeal_handled_at = now ();
$appeal -> save ();
Cache :: forget ( 'admin-dash:reports:ai-count' );
return redirect ( '/i/admin/reports/appeals' );
}
switch ( $appeal -> type ) {
case 'post.cw' :
$status = $appeal -> status ;
$status -> is_nsfw = false ;
$status -> save ();
break ;
case 'post.unlist' :
$status = $appeal -> status ;
$status -> scope = 'public' ;
$status -> visibility = 'public' ;
$status -> save ();
break ;
default :
# code...
break ;
}
$appeal -> appeal_handled_at = now ();
$appeal -> save ();
2021-12-13 05:30:55 +00:00
StatusService :: del ( $status -> id , true );
2021-11-09 07:06:56 +00:00
Cache :: forget ( 'admin-dash:reports:ai-count' );
return redirect ( '/i/admin/reports/appeals' );
}
2018-08-19 22:35:16 +00:00
public function updateReport ( Request $request , $id )
{
2018-08-28 03:07:36 +00:00
$this -> validate ( $request , [
'action' => 'required|string' ,
]);
2018-08-19 22:35:16 +00:00
2018-08-28 03:07:36 +00:00
$action = $request -> input ( 'action' );
2018-08-19 22:35:16 +00:00
2018-08-28 03:07:36 +00:00
$actions = [
'ignore' ,
'cw' ,
'unlist' ,
'delete' ,
'shadowban' ,
'ban' ,
];
2018-08-19 22:35:16 +00:00
2018-08-28 03:07:36 +00:00
if ( ! in_array ( $action , $actions )) {
return abort ( 403 );
}
2018-08-19 22:35:16 +00:00
2018-08-28 03:07:36 +00:00
$report = Report :: findOrFail ( $id );
2018-08-19 22:35:16 +00:00
2018-08-28 03:07:36 +00:00
$this -> handleReportAction ( $report , $action );
2021-11-09 06:02:34 +00:00
Cache :: forget ( 'admin-dash:reports:list-cache' );
2018-08-19 22:35:16 +00:00
2018-08-28 03:07:36 +00:00
return response () -> json ([ 'msg' => 'Success' ]);
2018-08-19 22:35:16 +00:00
}
public function handleReportAction ( Report $report , $action )
{
2018-08-28 03:07:36 +00:00
$item = $report -> reported ();
$report -> admin_seen = Carbon :: now ();
switch ( $action ) {
case 'ignore' :
$report -> not_interested = true ;
break ;
case 'cw' :
2018-12-05 03:11:50 +00:00
Cache :: forget ( 'status:thumb:' . $item -> id );
2018-08-28 03:07:36 +00:00
$item -> is_nsfw = true ;
$item -> save ();
$report -> nsfw = true ;
2021-12-13 05:30:55 +00:00
StatusService :: del ( $item -> id , true );
2018-08-28 03:07:36 +00:00
break ;
case 'unlist' :
$item -> visibility = 'unlisted' ;
$item -> save ();
2019-02-25 06:22:06 +00:00
Cache :: forget ( 'profiles:private' );
2021-12-13 05:30:55 +00:00
StatusService :: del ( $item -> id , true );
2018-08-28 03:07:36 +00:00
break ;
case 'delete' :
// Todo: fire delete job
$report -> admin_seen = null ;
2021-12-13 05:30:55 +00:00
StatusService :: del ( $item -> id , true );
2018-08-28 03:07:36 +00:00
break ;
case 'shadowban' :
// Todo: fire delete job
$report -> admin_seen = null ;
break ;
case 'ban' :
// Todo: fire delete job
$report -> admin_seen = null ;
break ;
default :
$report -> admin_seen = null ;
break ;
}
$report -> save ();
return $this ;
2018-08-19 22:35:16 +00:00
}
2018-09-02 03:35:32 +00:00
protected function actionMap ()
{
return [
'1' => 'ignore' ,
'2' => 'cw' ,
'3' => 'unlist' ,
'4' => 'delete' ,
'5' => 'shadowban' ,
'6' => 'ban'
];
}
public function bulkUpdateReport ( Request $request )
{
$this -> validate ( $request , [
'action' => 'required|integer|min:1|max:10' ,
'ids' => 'required|array'
]);
$action = $this -> actionMap ()[ $request -> input ( 'action' )];
$ids = $request -> input ( 'ids' );
$reports = Report :: whereIn ( 'id' , $ids ) -> whereNull ( 'admin_seen' ) -> get ();
foreach ( $reports as $report ) {
$this -> handleReportAction ( $report , $action );
}
$res = [
'message' => 'Success' ,
'code' => 200
];
return response () -> json ( $res );
}
2021-11-09 06:02:34 +00:00
public function reportMailVerifications ( Request $request )
{
$ids = Redis :: smembers ( 'email:manual' );
$ignored = Redis :: smembers ( 'email:manual-ignored' );
$reports = [];
if ( $ids ) {
$reports = collect ( $ids )
-> filter ( function ( $id ) use ( $ignored ) {
return ! in_array ( $id , $ignored );
})
-> map ( function ( $id ) {
$user = User :: whereProfileId ( $id ) -> first ();
2022-08-04 09:41:54 +00:00
if ( ! $user || $user -> email_verified_at ) {
2021-11-09 06:02:34 +00:00
return [];
}
2022-08-04 09:41:54 +00:00
$account = AccountService :: get ( $id , true );
2022-05-16 08:16:23 +00:00
if ( ! $account ) {
return [];
}
2021-11-09 06:02:34 +00:00
$account [ 'email' ] = $user -> email ;
return $account ;
})
-> filter ( function ( $res ) {
2021-12-29 07:15:01 +00:00
return $res && isset ( $res [ 'id' ]);
2021-11-09 06:02:34 +00:00
})
-> values ();
}
return view ( 'admin.reports.mail_verification' , compact ( 'reports' , 'ignored' ));
}
public function reportMailVerifyIgnore ( Request $request )
{
$id = $request -> input ( 'id' );
Redis :: sadd ( 'email:manual-ignored' , $id );
return redirect ( '/i/admin/reports' );
}
public function reportMailVerifyApprove ( Request $request )
{
$id = $request -> input ( 'id' );
$user = User :: whereProfileId ( $id ) -> firstOrFail ();
Redis :: srem ( 'email:manual' , $id );
Redis :: srem ( 'email:manual-ignored' , $id );
$user -> email_verified_at = now ();
$user -> save ();
return redirect ( '/i/admin/reports' );
}
public function reportMailVerifyClearIgnored ( Request $request )
{
Redis :: del ( 'email:manual-ignored' );
return [ 200 ];
}
2018-08-28 03:07:36 +00:00
}