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')); } $uid = $this->parameters['uid'] ?? 0; $user = []; if ($uid) { $user = User::getById($uid, ['username', 'blocked']); if (!$user) { $this->systemMessages->addNotice($this->t('User not found')); $this->baseUrl->redirect('ratioed'); } } $pager = new Pager($this->l10n, $this->args->getQueryString(), 100); $valid_orders = [ 'name', 'email', 'register_date', 'last-activity', 'last-item', 'page-flags', ]; $order = 'last-item'; $order_direction = '+'; if (!empty($_REQUEST['o'])) { $new_order = $_REQUEST['o']; if ($new_order[0] === '-') { $order_direction = '-'; $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'), $this->t('Replies last month'), $this->t('Reply likes'), $this->t('Respondee likes'), $this->t('OP likes'), $this->t('Reply guy score'), ]; $field_names = [ 'name', 'email', 'register_date', 'login_date', 'lastitem_date', 'page_flags', 'blocked_by', 'comments', 'reactions', 'ratio', 'reply_count', 'reply_likes', 'reply_respondee_likes', 'reply_op_likes', 'reply_guy_score', ]; $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'), '$help_url' => $this->baseUrl . '/ratioed/help', '$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), ]); } 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 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 FROM ( 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` ) 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; } protected function fillReplyGuyData(&$user) { $reply_guy_result = $this->getReplyGuyRow($user['user_contact_uid']); if (DBA::isResult($reply_guy_result)) { $reply_guy_result_row = DBA::fetch($reply_guy_result); $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; $denominator = $user['reply_likes'] + $user['reply_respondee_likes'] + $user['reply_op_likes']; if ($user['reply_count'] === 0) { $user['reply_guy'] = false; $user['reply_guy_score'] = 0; } elseif ($denominator == 0) { $user['reply_guy'] = true; $user['reply_guy_score'] = '∞'; } else { $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); } } else { $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; } } 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']; } else { $user['user_contact_uid'] = null; } 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; } else { $user['reactions'] = 0; if ($user['comments'] == 0) { $user['comments'] = 0; $user['ratio'] = 0; $user['ratioed'] = false; } else { $user['ratio'] = '∞'; $user['ratioed'] = false; } } } else { $user['comments'] = 'error'; $user['reactions'] = 'error'; $user['ratio'] = 'error'; $user['ratioed'] = false; } } else { $user['comments'] = 'error'; $user['reactions'] = 'error'; $user['ratio'] = 'error'; $user['ratioed'] = false; } $this->fillReplyGuyData($user); $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; }; } }