getOutput(); $request = $this->getRequest(); AbuseFilter::addNavigationLinks( $this->getContext(), 'log', $this->getLinkRenderer() ); $this->setHeaders(); $this->outputHeader( 'abusefilter-log-summary' ); $this->loadParameters(); $out->setPageTitle( $this->msg( 'abusefilter-log' ) ); $out->setRobotPolicy( "noindex,nofollow" ); $out->setArticleRelated( false ); $out->enableClientCache( false ); $out->addModuleStyles( 'ext.abuseFilter' ); // Are we allowed? $errors = $this->getPageTitle()->getUserPermissionsErrors( 'abusefilter-log', $this->getUser(), true, [ 'ns-specialprotected' ] ); if ( count( $errors ) ) { // Go away. $out->showPermissionsErrorPage( $errors, 'abusefilter-log' ); return; } $detailsid = $request->getIntOrNull( 'details' ); $hideid = $request->getIntOrNull( 'hide' ); $args = explode( '/', $parameter ); if ( count( $args ) === 2 && $args[0] === 'private' ) { $this->showPrivateDetails( $args[1] ); } elseif ( count( $args ) === 1 && $args[0] !== '' ) { if ( $args[0] === 'private' ) { $out->addWikiMsg( 'abusefilter-invalid-request-noid' ); } else { $this->showDetails( $args[0] ); } } else { if ( $hideid ) { $this->showHideForm( $hideid ); } else { $this->searchForm(); $this->showList(); } } } function loadParameters() { global $wgAbuseFilterIsCentral; $request = $this->getRequest(); $this->mSearchUser = trim( $request->getText( 'wpSearchUser' ) ); if ( $wgAbuseFilterIsCentral ) { $this->mSearchWiki = $request->getText( 'wpSearchWiki' ); } $u = User::newFromName( $this->mSearchUser ); if ( $u ) { $this->mSearchUser = $u->getName(); // Username normalisation } elseif ( IP::isIPAddress( $this->mSearchUser ) ) { // It's an IP $this->mSearchUser = IP::sanitizeIP( $this->mSearchUser ); } else { $this->mSearchUser = null; } $this->mSearchTitle = $request->getText( 'wpSearchTitle' ); $this->mSearchFilter = null; $this->mSearchActionTaken = $request->getText( 'wpSearchActionTaken' ); if ( self::canSeeDetails() ) { $this->mSearchFilter = $request->getText( 'wpSearchFilter' ); } $this->mSearchEntries = $request->getText( 'wpSearchEntries' ); $this->mSearchImpact = $request->getText( 'wpSearchImpact' ); } /** * @return string[] */ private function getAllActions() { global $wgAbuseFilterActions, $wgAbuseFilterCustomActionsHandlers; return array_unique( array_merge( array_keys( $wgAbuseFilterActions ), array_keys( $wgAbuseFilterCustomActionsHandlers ) ) ); } function searchForm() { global $wgAbuseFilterIsCentral; $formDescriptor = [ 'SearchUser' => [ 'label-message' => 'abusefilter-log-search-user', 'type' => 'user', 'default' => $this->mSearchUser, ], 'SearchTitle' => [ 'label-message' => 'abusefilter-log-search-title', 'type' => 'title', 'default' => $this->mSearchTitle, ], 'SearchImpact' => [ 'label-message' => 'abusefilter-log-search-impact', 'type' => 'select', 'options' => [ $this->msg( 'abusefilter-log-search-impact-all' )->text() => 0, $this->msg( 'abusefilter-log-search-impact-saved' )->text() => 1, $this->msg( 'abusefilter-log-search-impact-not-saved' )->text() => 2, ], ], ]; $options = [ $this->msg( 'abusefilter-log-noactions' )->text() => 'noactions', $this->msg( 'abusefilter-log-search-action-taken-any' )->text() => '', ]; foreach ( $this->getAllActions() as $action ) { $key = AbuseFilter::getActionDisplay( $action ); $options[$key] = $action; } ksort( $options ); $formDescriptor['SearchActionTaken'] = [ 'label-message' => 'abusefilter-log-search-action-taken-label', 'type' => 'select', 'options' => $options, ]; if ( self::canSeeHidden() ) { $formDescriptor['SearchEntries'] = [ 'type' => 'select', 'label-message' => 'abusefilter-log-search-entries-label', 'options' => [ $this->msg( 'abusefilter-log-search-entries-all' )->text() => 0, $this->msg( 'abusefilter-log-search-entries-hidden' )->text() => 1, $this->msg( 'abusefilter-log-search-entries-visible' )->text() => 2, ], ]; } if ( self::canSeeDetails() ) { $formDescriptor['SearchFilter'] = [ 'label-message' => 'abusefilter-log-search-filter', 'type' => 'text', 'default' => $this->mSearchFilter, ]; } if ( $wgAbuseFilterIsCentral ) { // Add free form input for wiki name. Would be nice to generate // a select with unique names in the db at some point. $formDescriptor['SearchWiki'] = [ 'label-message' => 'abusefilter-log-search-wiki', 'type' => 'text', 'default' => $this->mSearchWiki, ]; } $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() ) ->setWrapperLegendMsg( 'abusefilter-log-search' ) ->setSubmitTextMsg( 'abusefilter-log-search-submit' ) ->setMethod( 'get' ) ->prepareForm() ->displayForm( false ); } /** * @param string $id * @return mixed */ function showHideForm( $id ) { if ( !$this->getUser()->isAllowed( 'abusefilter-hide-log' ) ) { $this->getOutput()->addWikiMsg( 'abusefilter-log-hide-forbidden' ); return; } $dbr = wfGetDB( DB_REPLICA ); $row = $dbr->selectRow( [ 'abuse_filter_log', 'abuse_filter' ], 'afl_deleted', [ 'afl_id' => $id ], __METHOD__, [], [ 'abuse_filter' => [ 'LEFT JOIN', 'af_id=afl_filter' ] ] ); if ( !$row ) { return; } $formInfo = [ 'logid' => [ 'type' => 'info', 'default' => $id, 'label-message' => 'abusefilter-log-hide-id', ], 'reason' => [ 'type' => 'text', 'label-message' => 'abusefilter-log-hide-reason', ], 'hidden' => [ 'type' => 'toggle', 'default' => $row->afl_deleted, 'label-message' => 'abusefilter-log-hide-hidden', ], ]; $form = new HTMLForm( $formInfo, $this->getContext() ); $form->setTitle( $this->getPageTitle() ); $form->setWrapperLegend( $this->msg( 'abusefilter-log-hide-legend' )->text() ); $form->addHiddenField( 'hide', $id ); $form->setSubmitCallback( [ $this, 'saveHideForm' ] ); $form->show(); } /** * @param array $fields * @return bool */ function saveHideForm( $fields ) { $logid = $this->getRequest()->getVal( 'hide' ); $dbw = wfGetDB( DB_MASTER ); $dbw->update( 'abuse_filter_log', [ 'afl_deleted' => $fields['hidden'] ], [ 'afl_id' => $logid ], __METHOD__ ); $logPage = new LogPage( 'suppress' ); $action = $fields['hidden'] ? 'hide-afl' : 'unhide-afl'; $logPage->addEntry( $action, $this->getPageTitle( $logid ), $fields['reason'] ); $this->getOutput()->redirect( SpecialPage::getTitleFor( 'AbuseLog' )->getFullURL() ); return true; } function showList() { $out = $this->getOutput(); // Generate conditions list. $conds = []; if ( $this->mSearchUser ) { $user = User::newFromName( $this->mSearchUser ); if ( !$user ) { $conds['afl_user'] = 0; $conds['afl_user_text'] = $this->mSearchUser; } else { $conds['afl_user'] = $user->getId(); $conds['afl_user_text'] = $user->getName(); } } if ( $this->mSearchWiki ) { if ( $this->mSearchWiki == wfWikiID() ) { $conds['afl_wiki'] = null; } else { $conds['afl_wiki'] = $this->mSearchWiki; } } if ( $this->mSearchFilter ) { $searchFilters = array_map( 'trim', explode( '|', $this->mSearchFilter ) ); // if a filter is hidden, users who can't view private filters should // not be able to find log entries generated by it. if ( !AbuseFilterView::canViewPrivate() && !$this->getUser()->isAllowed( 'abusefilter-log-private' ) ) { $searchedForPrivate = false; foreach ( $searchFilters as $index => $filter ) { if ( AbuseFilter::filterHidden( $filter ) ) { unset( $searchFilters[$index] ); $searchedForPrivate = true; } } if ( $searchedForPrivate ) { $out->addWikiMsg( 'abusefilter-log-private-not-included' ); } } if ( empty( $searchFilters ) ) { $out->addWikiMsg( 'abusefilter-log-noresults' ); return; } $conds['afl_filter'] = $searchFilters; } $searchTitle = Title::newFromText( $this->mSearchTitle ); if ( $this->mSearchTitle && $searchTitle ) { $conds['afl_namespace'] = $searchTitle->getNamespace(); $conds['afl_title'] = $searchTitle->getDBkey(); } $dbr = wfGetDB( DB_REPLICA ); if ( self::canSeeHidden() ) { if ( $this->mSearchEntries == '1' ) { $conds['afl_deleted'] = 1; } elseif ( $this->mSearchEntries == '2' ) { $conds[] = self::getNotDeletedCond( $dbr ); } } if ( in_array( $this->mSearchImpact, [ '1', '2' ] ) ) { $unsuccessfulActionConds = $dbr->makeList( [ 'afl_rev_id' => null, 'afl_log_id' => null, ], LIST_AND ); if ( $this->mSearchImpact == '1' ) { $conds[] = "NOT ( $unsuccessfulActionConds )"; } else { $conds[] = $unsuccessfulActionConds; } } if ( $this->mSearchActionTaken ) { if ( in_array( $this->mSearchActionTaken, $this->getAllActions() ) ) { $actions = [ $this->mSearchActionTaken ]; if ( $this->mSearchActionTaken !== 'tag' ) { $actions[] = "{$this->mSearchActionTaken},tag"; } $conds['afl_actions'] = $actions; } elseif ( $this->mSearchActionTaken === 'noactions' ) { $conds['afl_actions'] = ''; } } $pager = new AbuseLogPager( $this, $conds ); $pager->doQuery(); $result = $pager->getResult(); if ( $result && $result->numRows() !== 0 ) { $out->addHTML( $pager->getNavigationBar() . Xml::tags( 'ul', [ 'class' => 'plainlinks' ], $pager->getBody() ) . $pager->getNavigationBar() ); } else { $out->addWikiMsg( 'abusefilter-log-noresults' ); } } /** * @param string $id * @return mixed */ function showDetails( $id ) { $out = $this->getOutput(); $dbr = wfGetDB( DB_REPLICA ); $row = $dbr->selectRow( [ 'abuse_filter_log', 'abuse_filter' ], '*', [ 'afl_id' => $id ], __METHOD__, [], [ 'abuse_filter' => [ 'LEFT JOIN', 'af_id=afl_filter' ] ] ); if ( !$row ) { $out->addWikiMsg( 'abusefilter-log-nonexistent' ); return; } if ( AbuseFilter::decodeGlobalName( $row->afl_filter ) ) { $filter_hidden = null; } else { $filter_hidden = $row->af_hidden; } if ( !self::canSeeDetails( $row->afl_filter, $filter_hidden ) ) { $out->addWikiMsg( 'abusefilter-log-cannot-see-details' ); return; } if ( self::isHidden( $row ) && !self::canSeeHidden() ) { $out->addWikiMsg( 'abusefilter-log-details-hidden' ); return; } $output = Xml::element( 'legend', null, $this->msg( 'abusefilter-log-details-legend' ) ->numParams( $id ) ->text() ); $output .= Xml::tags( 'p', null, $this->formatRow( $row, false ) ); // Load data $vars = AbuseFilter::loadVarDump( $row->afl_var_dump ); $out->addJsConfigVars( 'wgAbuseFilterVariables', $vars->dumpAllVars( true ) ); // Diff, if available if ( $vars && $vars->getVar( 'action' )->toString() == 'edit' ) { $old_wikitext = $vars->getVar( 'old_wikitext' )->toString(); $new_wikitext = $vars->getVar( 'new_wikitext' )->toString(); $diffEngine = new DifferenceEngine( $this->getContext() ); $diffEngine->showDiffStyle(); $formattedDiff = $diffEngine->generateTextDiffBody( $old_wikitext, $new_wikitext ); $formattedDiff = $diffEngine->addHeader( $formattedDiff, '', '' ); $output .= Xml::tags( 'h3', null, $this->msg( 'abusefilter-log-details-diff' )->parse() ); $output .= $formattedDiff; } $output .= Xml::element( 'h3', null, $this->msg( 'abusefilter-log-details-vars' )->text() ); // Build a table. $output .= AbuseFilter::buildVarDumpTable( $vars, $this->getContext() ); if ( self::canSeePrivate() ) { $formDescriptor = [ 'Reason' => [ 'label-message' => 'abusefilter-view-private-reason', 'type' => 'text', 'size' => 45, ], ]; $htmlForm = HTMLForm::factory( 'table', $formDescriptor, $this->getContext() ); $htmlForm->setWrapperLegendMsg( 'abusefilter-view-private' ) ->setAction( $this->getPageTitle( 'private/' . $id )->getLocalURL() ) ->setSubmitTextMsg( 'abusefilter-view-private-submit' ) ->setMethod( 'post' ) ->prepareForm(); $output .= $htmlForm->getHTML( false ); } $output = Xml::tags( 'fieldset', null, $output ); $out->addHTML( $output ); } /** * @param string $id * @return null */ function showPrivateDetails( $id ) { global $wgAbuseFilterPrivateLog; $lang = $this->getLanguage(); $out = $this->getOutput(); $request = $this->getRequest(); $dbr = wfGetDB( DB_REPLICA ); $reason = $request->getText( 'wpReason' ); // Make sure it is a valid request $token = $request->getVal( 'wpEditToken' ); if ( !$request->wasPosted() || !$this->getUser()->matchEditToken( $token ) ) { $out->wrapWikiMsg( '