2024-06-23 13:24:43 +00:00
< ? php
namespace Friendica\Addon\ratioed ;
use Friendica\Content\Pager ;
use Friendica\Core\Logger ;
use Friendica\Core\Renderer ;
use Friendica\Database\DBA ;
2024-07-22 16:23:23 +00:00
use Friendica\DI ;
2024-06-23 13:24:43 +00:00
use Friendica\Model\User ;
2025-01-04 23:23:51 +00:00
use Friendica\Model\Verb ;
2024-06-23 13:24:43 +00:00
use Friendica\Module\Moderation\Users\Active ;
2025-01-04 23:23:51 +00:00
use Friendica\Protocol\Activity ;
2024-06-23 13:24:43 +00:00
/**
* This class implements the " Behaviour " panel in Moderation / Users
*/
class RatioedPanel extends Active
{
protected function content ( array $request = []) : string
{
Active :: content ();
2024-07-22 16:23:23 +00:00
if ( isset ( DI :: args () -> getArgv ()[ 1 ]) and DI :: args () -> getArgv ()[ 1 ] === 'help' ) {
$template = Renderer :: getMarkupTemplate ( '/help.tpl' , 'addon/ratioed/' );
return Renderer :: replaceMacros ( $template , array ( '$config' => DI :: baseUrl () . '/settings/addon' ));
}
2024-06-23 13:24:43 +00:00
$uid = $this -> parameters [ 'uid' ] ? ? 0 ;
2024-11-30 20:13:41 +00:00
$user = [];
2024-06-23 13:24:43 +00:00
if ( $uid ) {
$user = User :: getById ( $uid , [ 'username' , 'blocked' ]);
if ( ! $user ) {
$this -> systemMessages -> addNotice ( $this -> t ( 'User not found' ));
2025-01-11 19:07:18 +00:00
$this -> baseUrl -> redirect ( 'ratioed' );
2024-06-23 13:24:43 +00:00
}
}
$pager = new Pager ( $this -> l10n , $this -> args -> getQueryString (), 100 );
$valid_orders = [
'name' ,
'email' ,
'register_date' ,
'last-activity' ,
'last-item' ,
'page-flags' ,
];
$order = 'last-item' ;
2025-01-11 19:07:18 +00:00
$order_direction = '+' ;
2025-01-11 18:36:38 +00:00
if ( ! empty ( $_REQUEST [ 'o' ])) {
$new_order = $_REQUEST [ 'o' ];
2025-01-11 19:07:18 +00:00
if ( $new_order [ 0 ] === '-' ) {
$order_direction = '-' ;
2024-06-23 13:24:43 +00:00
$new_order = substr ( $new_order , 1 );
}
if ( in_array ( $new_order , $valid_orders )) {
$order = $new_order ;
}
}
$users = User :: getList ( $pager -> getStart (), $pager -> getItemsPerPage (), 'active' , $order , ( $order_direction == '-' ));
$users = array_map ( $this -> setupUserCallback (), $users );
$header_titles = [
$this -> t ( 'Name' ),
$this -> t ( 'Email' ),
$this -> t ( 'Register date' ),
$this -> t ( 'Last login' ),
$this -> t ( 'Last public item' ),
$this -> t ( 'Type' ),
$this -> t ( 'Blocked by' ),
$this -> t ( 'Comments last 24h' ),
$this -> t ( 'Reactions last 24h' ),
$this -> t ( 'Ratio last 24h' ),
2025-01-04 23:23:51 +00:00
$this -> t ( 'Replies last month' ),
$this -> t ( 'Reply likes' ),
$this -> t ( 'Respondee likes' ),
$this -> t ( 'OP likes' ),
$this -> t ( 'Reply guy score' ),
2024-06-23 13:24:43 +00:00
];
$field_names = [
'name' ,
'email' ,
'register_date' ,
'login_date' ,
'lastitem_date' ,
'page_flags' ,
'blocked_by' ,
'comments' ,
'reactions' ,
'ratio' ,
2025-01-04 23:23:51 +00:00
'reply_count' ,
'reply_likes' ,
'reply_respondee_likes' ,
'reply_op_likes' ,
'reply_guy_score' ,
2024-06-23 13:24:43 +00:00
];
$th_users = array_map ( null , $header_titles , $valid_orders , $field_names );
$count = $this -> database -> count ( 'user' , [ " `verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired` AND `uid` != ? " , 0 ]);
$t = Renderer :: getMarkupTemplate ( 'ratioed.tpl' , 'addon/ratioed' );
return self :: getTabsHTML ( 'ratioed' ) . Renderer :: replaceMacros ( $t , [
// strings //
'$title' => $this -> t ( 'Moderation' ),
2024-07-22 16:23:23 +00:00
'$help_url' => $this -> baseUrl . '/ratioed/help' ,
2024-06-23 13:24:43 +00:00
'$page' => $this -> t ( 'Behaviour' ),
'$select_all' => $this -> t ( 'select all' ),
'$delete' => $this -> t ( 'Delete' ),
'$block' => $this -> t ( 'Block' ),
'$blocked' => $this -> t ( 'User blocked' ),
'$siteadmin' => $this -> t ( 'Site admin' ),
'$accountexpired' => $this -> t ( 'Account expired' ),
'$h_newuser' => $this -> t ( 'Create a new user' ),
'$th_users' => $th_users ,
'$order_users' => $order ,
'$order_direction_users' => $order_direction ,
'$confirm_delete_multi' => $this -> t ( 'Selected users will be deleted!\n\nEverything these users had posted on this site will be permanently deleted!\n\nAre you sure?' ),
'$confirm_delete' => $this -> t ( 'The user {0} will be deleted!\n\nEverything this user has posted on this site will be permanently deleted!\n\nAre you sure?' ),
'$form_security_token' => self :: getFormSecurityToken ( 'moderation_users_active' ),
// values //
'$baseurl' => $this -> baseUrl ,
'$query_string' => $this -> args -> getQueryString (),
'$users' => $users ,
'$count' => $count ,
'$pager' => $pager -> renderFull ( $count ),
]);
}
2025-01-04 23:23:51 +00:00
protected function getReplyGuyRow ( $contact_uid )
{
$like_vid = Verb :: getID ( Activity :: LIKE );
$post_vid = Verb :: getID ( Activity :: POST );
/*
* This is a complicated query .
*
* The innermost select retrieves a chain of four posts : an
* original post , a target comment ( possibly deep down in the
* thread ), a reply from our user , and a like for that reply .
* If there ' s no like , we still want to count the reply , so we
* use an outer join .
*
* The second select adds " points " for different kinds of
* likes . The outermost select then counts up these points ,
* and the number of distinct replies .
*/
$reply_guy_result = DBA :: p ( '
SELECT
COUNT ( distinct reply_id ) AS replies_total ,
SUM ( like_point ) AS like_total ,
SUM ( target_like_point ) AS target_like_total ,
SUM ( original_like_point ) AS original_like_total
FROM (
SELECT
2025-01-18 15:25:50 +00:00
reply_id ,
like_date ,
like_date IS NOT NULL AS like_point ,
like_author = target_author AS target_like_point ,
like_author = original_author AS original_like_point
2025-01-04 23:23:51 +00:00
FROM (
2025-01-18 15:25:50 +00:00
SELECT
original_post . `uri-id` AS original_id ,
original_post . `author-id` AS original_author ,
original_post . created AS original_date ,
target_post . `uri-id` AS target_id ,
target_post . `author-id` AS target_author ,
target_post . created AS target_date ,
reply_post . `uri-id` AS reply_id ,
reply_post . `author-id` AS reply_author ,
reply_post . created AS reply_date ,
like_post . `uri-id` AS like_id ,
like_post . `author-id` AS like_author ,
like_post . created AS like_date
FROM
post AS original_post
JOIN
post AS target_post
ON
original_post . `uri-id` = target_post . `parent-uri-id`
JOIN
post AS reply_post
ON
target_post . `uri-id` = reply_post . `thr-parent-id` AND
reply_post . `author-id` = ? AND
reply_post . `author-id` != target_post . `author-id` AND
reply_post . `author-id` != original_post . `author-id` AND
reply_post . `uri-id` != reply_post . `thr-parent-id` AND
reply_post . vid = ? AND
reply_post . created > CURDATE () - INTERVAL 1 MONTH
LEFT OUTER JOIN
post AS like_post
ON
reply_post . `uri-id` = like_post . `thr-parent-id` AND
like_post . vid = ? AND
like_post . `author-id` != reply_post . `author-id`
2025-01-04 23:23:51 +00:00
) AS post_meta
) AS reply_counts
' , $contact_uid , $post_vid , $like_vid );
return $reply_guy_result ;
}
// https://stackoverflow.com/a/48283297/235936
protected function sigFig ( $value , $digits )
{
if ( $value == 0 ) {
$decimalPlaces = $digits - 1 ;
} elseif ( $value < 0 ) {
$decimalPlaces = $digits - floor ( log10 ( $value * - 1 )) - 1 ;
} else {
$decimalPlaces = $digits - floor ( log10 ( $value )) - 1 ;
}
$answer = ( $decimalPlaces > 0 ) ?
number_format ( $value , $decimalPlaces ) : round ( $value , $decimalPlaces );
return $answer ;
}
2025-01-18 15:25:50 +00:00
protected function fillReplyGuyData ( & $user )
{
2025-01-04 23:23:51 +00:00
$reply_guy_result = $this -> getReplyGuyRow ( $user [ 'user_contact_uid' ]);
if ( DBA :: isResult ( $reply_guy_result )) {
$reply_guy_result_row = DBA :: fetch ( $reply_guy_result );
2025-01-22 08:46:57 +00:00
$user [ 'reply_count' ] = ( int ) $reply_guy_result_row [ 'replies_total' ] ? ? 0 ;
$user [ 'reply_likes' ] = ( int ) $reply_guy_result_row [ 'like_total' ] ? ? 0 ;
$user [ 'reply_respondee_likes' ] = ( int ) $reply_guy_result_row [ 'target_like_total' ] ? ? 0 ;
$user [ 'reply_op_likes' ] = ( int ) $reply_guy_result_row [ 'original_like_total' ] ? ? 0 ;
2025-01-04 23:23:51 +00:00
2025-01-22 08:46:57 +00:00
$denominator = $user [ 'reply_likes' ] + $user [ 'reply_respondee_likes' ] + $user [ 'reply_op_likes' ];
if ( $user [ 'reply_count' ] === 0 ) {
2025-01-04 23:23:51 +00:00
$user [ 'reply_guy' ] = false ;
$user [ 'reply_guy_score' ] = 0 ;
2025-01-18 15:25:50 +00:00
} elseif ( $denominator == 0 ) {
2025-01-04 23:23:51 +00:00
$user [ 'reply_guy' ] = true ;
$user [ 'reply_guy_score' ] = '∞' ;
2025-01-18 15:25:50 +00:00
} else {
2025-01-04 23:23:51 +00:00
$reply_guy_score = $user [ 'reply_count' ] / $denominator ;
$user [ 'reply_guy' ] = $reply_guy_score >= 1.0 ;
$user [ 'reply_guy_score' ] = $this -> sigFig ( $reply_guy_score , 2 );
}
2025-01-18 15:25:50 +00:00
} else {
2025-01-04 23:23:51 +00:00
$user [ 'reply_count' ] = " error " ;
$user [ 'reply_likes' ] = " error " ;
$user [ 'reply_respondee_likes' ] = " error " ;
$user [ 'reply_op_likes' ] = " error " ;
$user [ 'reply_guy' ] = false ;
$user [ 'reply_guy_score' ] = 0 ;
}
}
2024-06-23 13:24:43 +00:00
protected function setupUserCallback () : \Closure
{
Logger :: debug ( " ratioed: setupUserCallback " );
$parentCallback = parent :: setupUserCallback ();
return function ( $user ) use ( $parentCallback ) {
$blocked_count = DBA :: count ( 'user-contact' , [ 'uid' => $user [ 'uid' ], 'is-blocked' => 1 ]);
$user [ 'blocked_by' ] = $blocked_count ;
$self_contact_result = DBA :: p ( 'SELECT admin_contact.id AS user_contact_uid FROM contact AS admin_contact JOIN contact AS user_contact ON admin_contact.`uri-id` = user_contact.`uri-id` AND admin_contact.self = 0 AND user_contact.self = 1 WHERE user_contact.uid = ?' , $user [ 'uid' ]);
if ( DBA :: isResult ( $self_contact_result )) {
$self_contact_result_row = DBA :: fetch ( $self_contact_result );
$user [ 'user_contact_uid' ] = $self_contact_result_row [ 'user_contact_uid' ];
2025-01-18 15:25:50 +00:00
} else {
$user [ 'user_contact_uid' ] = null ;
2024-06-23 13:24:43 +00:00
}
if ( $user [ 'user_contact_uid' ]) {
$post_engagement_result = DBA :: p ( 'SELECT SUM(`comments`) AS `comment_count`, SUM(`activities`) AS `activities_count` FROM `post-engagement` WHERE `post-engagement`.created > DATE_SUB(now(), INTERVAL 1 DAY) AND `post-engagement`.`owner-id` = ?' , $user [ 'user_contact_uid' ]);
if ( DBA :: isResult ( $post_engagement_result )) {
$post_engagement_result_row = DBA :: fetch ( $post_engagement_result );
$user [ 'comments' ] = $post_engagement_result_row [ 'comment_count' ];
$user [ 'reactions' ] = $post_engagement_result_row [ 'activities_count' ];
if ( $user [ 'reactions' ] > 0 ) {
$user [ 'ratio' ] = number_format ( $user [ 'comments' ] / $user [ 'reactions' ], 1 , '.' , '' );
$user [ 'ratioed' ] = ( float )( $user [ 'ratio' ]) >= 2.0 ;
2025-01-18 15:25:50 +00:00
} else {
2025-01-11 18:36:06 +00:00
$user [ 'reactions' ] = 0 ;
2024-06-23 13:24:43 +00:00
if ( $user [ 'comments' ] == 0 ) {
2025-01-11 18:36:06 +00:00
$user [ 'comments' ] = 0 ;
$user [ 'ratio' ] = 0 ;
2024-06-23 13:24:43 +00:00
$user [ 'ratioed' ] = false ;
2025-01-18 15:25:50 +00:00
} else {
2024-06-23 13:24:43 +00:00
$user [ 'ratio' ] = '∞' ;
$user [ 'ratioed' ] = false ;
}
}
2025-01-18 15:25:50 +00:00
} else {
2024-06-23 13:24:43 +00:00
$user [ 'comments' ] = 'error' ;
$user [ 'reactions' ] = 'error' ;
$user [ 'ratio' ] = 'error' ;
$user [ 'ratioed' ] = false ;
}
2025-01-18 15:25:50 +00:00
} else {
2024-06-23 13:24:43 +00:00
$user [ 'comments' ] = 'error' ;
$user [ 'reactions' ] = 'error' ;
$user [ 'ratio' ] = 'error' ;
$user [ 'ratioed' ] = false ;
}
2025-01-04 23:23:51 +00:00
$this -> fillReplyGuyData ( $user );
2024-06-23 13:24:43 +00:00
$user = $parentCallback ( $user );
Logger :: debug ( " ratioed: setupUserCallback " , [
'uid' => $user [ 'uid' ],
'blocked_by' => $user [ 'blocked_by' ],
'comments' => $user [ 'comments' ],
'reactions' => $user [ 'reactions' ],
'ratio' => $user [ 'ratio' ],
'ratioed' => $user [ 'ratioed' ],
]);
return $user ;
};
}
}