summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'CheckUser/src/ComparePager.php')
-rw-r--r--CheckUser/src/ComparePager.php337
1 files changed, 337 insertions, 0 deletions
diff --git a/CheckUser/src/ComparePager.php b/CheckUser/src/ComparePager.php
new file mode 100644
index 00000000..e3de207c
--- /dev/null
+++ b/CheckUser/src/ComparePager.php
@@ -0,0 +1,337 @@
+<?php
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Pager
+ */
+
+namespace MediaWiki\CheckUser;
+
+use DateTime;
+use Html;
+use IContextSource;
+use Linker;
+use MediaWiki\Linker\LinkRenderer;
+use TablePager;
+use Wikimedia\IPUtils;
+use Wikimedia\Rdbms\FakeResultWrapper;
+
+class ComparePager extends TablePager {
+ /** @var CompareService */
+ private $compareService;
+
+ /** @var TokenQueryManager */
+ private $tokenQueryManager;
+
+ /** @var array */
+ private $fieldNames;
+
+ /**
+ * Holds a cache of iphex => edit-count to avoid
+ * recurring queries to the database for the same ip
+ *
+ * @var array
+ */
+ private $ipTotalEdits;
+
+ /**
+ * Targets whose results should not be included in the investigation.
+ * Targets in this list may or may not also be in the $targets list.
+ * Either way, no activity related to these targets will appear in the
+ * results.
+ *
+ * @var string[]
+ */
+ private $excludeTargets;
+
+ /**
+ * Targets that have been added to the investigation but that are not
+ * present in $excludeTargets. These are the targets that will actually
+ * be investigated.
+ *
+ * @var string[]
+ */
+ private $filteredTargets;
+
+ /** @var string */
+ private $start;
+
+ public function __construct(
+ IContextSource $context,
+ LinkRenderer $linkRenderer,
+ TokenQueryManager $tokenQueryManager,
+ DurationManager $durationManager,
+ CompareService $compareService
+ ) {
+ parent::__construct( $context, $linkRenderer );
+ $this->compareService = $compareService;
+ $this->tokenQueryManager = $tokenQueryManager;
+
+ $tokenData = $tokenQueryManager->getDataFromRequest( $context->getRequest() );
+ $this->mOffset = $tokenData['offset'] ?? '';
+
+ $this->excludeTargets = $tokenData['exclude-targets'] ?? [];
+ $this->filteredTargets = array_diff(
+ $tokenData['targets'] ?? [],
+ $this->excludeTargets
+ );
+
+ $this->start = $durationManager->getTimestampFromRequest( $context->getRequest() );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getTableClass() {
+ $sortableClass = $this->mIsFirst && $this->mIsLast ? 'sortable' : '';
+ return implode( ' ', [
+ parent::getTableClass(),
+ $sortableClass,
+ 'ext-checkuser-investigate-table',
+ 'ext-checkuser-investigate-table-compare'
+ ] );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getCellAttrs( $field, $value ) {
+ $attributes = parent::getCellAttrs( $field, $value );
+ $attributes['class'] = $attributes['class'] ?? '';
+
+ $row = $this->mCurrentRow;
+ switch ( $field ) {
+ case 'cuc_ip':
+ foreach ( $this->filteredTargets as $target ) {
+ if ( !IPUtils::isIPAddress( $target ) ) {
+ continue;
+ }
+
+ if ( IPUtils::isValidRange( $target ) && IPUtils::isInRange( $value, $target ) ) {
+ $attributes['class'] .= ' ext-checkuser-compare-table-cell-target';
+ break;
+ } elseif ( IPUtils::toHex( $target ) === $row->cuc_ip_hex ) {
+ $attributes['class'] .= ' ext-checkuser-compare-table-cell-target';
+ break;
+ }
+ }
+ $ipHex = IPUtils::toHex( $value );
+ $attributes['class'] .= ' ext-checkuser-investigate-table-cell-interactive';
+ $attributes['class'] .= ' ext-checkuser-investigate-table-cell-pinnable';
+ $attributes['class'] .= ' ext-checkuser-compare-table-cell-ip-target';
+ $attributes['data-field'] = $field;
+ $attributes['data-value'] = $value;
+ $attributes['data-sort-value'] = $ipHex;
+ $attributes['data-edits'] = $row->total_edits;
+ $attributes['data-all-edits'] = $this->ipTotalEdits[$ipHex];
+ break;
+ case 'cuc_user_text':
+ $attributes['class'] .= ' ext-checkuser-investigate-table-cell-interactive';
+ if ( !IPUtils::isIpAddress( $value ) ) {
+ $attributes['class'] .= ' ext-checkuser-compare-table-cell-user-target';
+ if ( in_array( $value, $this->filteredTargets ) ) {
+ $attributes['class'] .= ' ext-checkuser-compare-table-cell-target';
+ }
+ $attributes['data-field'] = $field;
+ $attributes['data-value'] = $value;
+ }
+ // Store the sort value as an attribute, to avoid using the table cell contents
+ // as the sort value, since UI elements are added to the table cell.
+ $attributes['data-sort-value'] = $value;
+ break;
+ case 'cuc_agent':
+ $attributes['class'] .= ' ext-checkuser-investigate-table-cell-interactive';
+ $attributes['class'] .= ' ext-checkuser-investigate-table-cell-pinnable';
+ $attributes['class'] .= ' ext-checkuser-compare-table-cell-user-agent';
+ $attributes['data-field'] = $field;
+ $attributes['data-value'] = $value;
+ // Store the sort value as an attribute, to avoid using the table cell contents
+ // as the sort value, since UI elements are added to the table cell.
+ $attributes['data-sort-value'] = $value;
+ break;
+ case 'activity':
+ $attributes['class'] .= ' ext-checkuser-compare-table-cell-activity';
+ $start = new DateTime( $row->first_edit );
+ $end = new DateTime( $row->last_edit );
+ $attributes['data-sort-value'] = $start->format( 'Ymd' ) . $end->format( 'Ymd' );
+ break;
+ }
+
+ // Add each cell to the tab index.
+ $attributes['tabindex'] = 0;
+
+ return $attributes;
+ }
+
+ /**
+ * @param string $name
+ * @param mixed $value
+ * @return string
+ */
+ public function formatValue( $name, $value ) {
+ $language = $this->getLanguage();
+ $row = $this->mCurrentRow;
+
+ switch ( $name ) {
+ case 'cuc_user_text':
+ if ( IPUtils::isValid( $value ) ) {
+ $formatted = $this->msg( 'checkuser-investigate-compare-table-cell-unregistered' );
+ } else {
+ $formatted = Linker::userLink( $row->cuc_user, $value );
+ }
+ break;
+ case 'cuc_ip':
+ $formatted = Html::rawElement(
+ 'span',
+ [ 'class' => "ext-checkuser-compare-table-cell-ip" ],
+ htmlspecialchars( $value )
+ );
+
+ // get other edits
+ $otherEdits = '';
+ $ipHex = $row->cuc_ip_hex;
+ if ( !isset( $this->ipTotalEdits[$ipHex] ) ) {
+ $this->ipTotalEdits[$ipHex] = $this->compareService->getTotalEditsFromIp( $ipHex );
+ }
+
+ if ( $this->ipTotalEdits[$ipHex] ) {
+ $otherEdits = Html::rawElement(
+ 'span',
+ [],
+ $this->msg(
+ 'checkuser-investigate-compare-table-cell-other-edits',
+ $this->ipTotalEdits[$ipHex]
+ )->parse()
+ );
+ }
+
+ $formatted .= Html::rawElement(
+ 'div',
+ [],
+ $this->msg(
+ 'checkuser-investigate-compare-table-cell-edits',
+ $row->total_edits
+ )->parse() . $otherEdits
+ );
+
+ break;
+ case 'cuc_agent':
+ $formatted = htmlspecialchars( $value );
+ break;
+ case 'activity':
+ $firstEdit = $language->userDate( $row->first_edit, $this->getUser() );
+ $lastEdit = $language->userDate( $row->last_edit, $this->getUser() );
+ $formatted = htmlspecialchars( $firstEdit . ' - ' . $lastEdit );
+ break;
+ default:
+ $formatted = '';
+ }
+
+ return $formatted;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getIndexField() {
+ return [ [ 'cuc_user_text', 'cuc_ip_hex', 'cuc_agent' ] ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFieldNames() {
+ if ( $this->fieldNames === null ) {
+ $this->fieldNames = [
+ 'cuc_user_text' => 'checkuser-investigate-compare-table-header-username',
+ 'cuc_ip' => 'checkuser-investigate-compare-table-header-ip',
+ 'cuc_agent' => 'checkuser-investigate-compare-table-header-useragent',
+ 'activity' => 'checkuser-investigate-compare-table-header-activity',
+ ];
+ foreach ( $this->fieldNames as $key => $val ) {
+ $this->fieldNames[$key] = $this->msg( $val )->text();
+ }
+ }
+ return $this->fieldNames;
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * Handle special case where all targets are filtered.
+ */
+ public function doQuery() {
+ // If there are no targets, there is no need to run the query and an empty result can be used.
+ if ( $this->filteredTargets === [] ) {
+ $this->mResult = new FakeResultWrapper( [] );
+ $this->mQueryDone = true;
+ return $this->mResult;
+ }
+
+ return parent::doQuery();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getQueryInfo() {
+ return $this->compareService->getQueryInfo(
+ $this->filteredTargets,
+ $this->excludeTargets,
+ $this->start
+ );
+ }
+
+ /**
+ * Check if we have incomplete data for any of the targets.
+ *
+ * @return string[] Targets whose limits were exceeded (if any)
+ */
+ public function getTargetsOverLimit() : array {
+ return $this->compareService->getTargetsOverLimit(
+ $this->filteredTargets,
+ $this->excludeTargets,
+ $this->start
+ );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isFieldSortable( $field ) {
+ return false;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getDefaultSort() {
+ return '';
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * Conceal the offset which may reveal private data.
+ */
+ public function getPagingQueries() {
+ return $this->tokenQueryManager->getPagingQueries(
+ $this->getRequest(), parent::getPagingQueries()
+ );
+ }
+}