Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
23.97% covered (danger)
23.97%
29 / 121
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
HideAbuseLog
23.97% covered (danger)
23.97%
29 / 121
33.33% covered (danger)
33.33%
1 / 3
100.15
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 show
32.86% covered (danger)
32.86%
23 / 70
0.00% covered (danger)
0.00%
0 / 1
16.90
 saveHideForm
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2
3namespace MediaWiki\Extension\AbuseFilter\View;
4
5use MediaWiki\Cache\LinkBatchFactory;
6use MediaWiki\Context\IContextSource;
7use MediaWiki\Deferred\DeferredUpdates;
8use MediaWiki\Extension\AbuseFilter\AbuseFilterPermissionManager;
9use MediaWiki\Extension\AbuseFilter\Pager\AbuseLogPager;
10use MediaWiki\Extension\AbuseFilter\Variables\VariablesBlobStore;
11use MediaWiki\Html\Html;
12use MediaWiki\HTMLForm\HTMLForm;
13use MediaWiki\Linker\LinkRenderer;
14use MediaWiki\Logging\LogEventsList;
15use MediaWiki\Logging\LogPage;
16use MediaWiki\Logging\ManualLogEntry;
17use MediaWiki\Permissions\PermissionManager;
18use Wikimedia\Rdbms\LBFactory;
19
20class HideAbuseLog extends AbuseFilterView {
21
22    private LBFactory $lbFactory;
23    private LinkBatchFactory $linkBatchFactory;
24    private PermissionManager $permissionManager;
25    private VariablesBlobStore $variablesBlobStore;
26
27    /** @var int[] */
28    private $hideIDs;
29
30    public function __construct(
31        LBFactory $lbFactory,
32        AbuseFilterPermissionManager $afPermManager,
33        IContextSource $context,
34        LinkRenderer $linkRenderer,
35        LinkBatchFactory $linkBatchFactory,
36        PermissionManager $permissionManager,
37        VariablesBlobStore $variablesBlobStore,
38        string $basePageName
39    ) {
40        parent::__construct( $afPermManager, $context, $linkRenderer, $basePageName, [] );
41        $this->lbFactory = $lbFactory;
42        $this->linkBatchFactory = $linkBatchFactory;
43        $this->permissionManager = $permissionManager;
44        $this->variablesBlobStore = $variablesBlobStore;
45
46        $this->hideIDs = array_keys( $this->getRequest()->getArray( 'hideids', [] ) );
47    }
48
49    /**
50     * Shows the page
51     */
52    public function show(): void {
53        $output = $this->getOutput();
54        $output->enableOOUI();
55
56        if ( !$this->afPermManager->canHideAbuseLog( $this->getAuthority() ) ) {
57            $output->addWikiMsg( 'abusefilter-log-hide-forbidden' );
58            return;
59        }
60
61        if ( !$this->hideIDs ) {
62            $output->addWikiMsg( 'abusefilter-log-hide-no-selected' );
63            return;
64        }
65
66        $pager = new AbuseLogPager(
67            $this->getContext(),
68            $this->linkRenderer,
69            [ 'afl_id' => $this->hideIDs ],
70            $this->linkBatchFactory,
71            $this->permissionManager,
72            $this->afPermManager,
73            $this->variablesBlobStore,
74            $this->basePageName,
75            array_fill_keys( $this->hideIDs, $this->getRequest()->getVal( 'wpshoworhide' ) )
76        );
77        $pager->doQuery();
78        if ( $pager->getResult()->numRows() === 0 ) {
79            $output->addWikiMsg( 'abusefilter-log-hide-no-selected' );
80            return;
81        }
82
83        $output->addModuleStyles( 'mediawiki.interface.helpers.styles' );
84        $output->wrapWikiMsg(
85            "<strong>$1</strong>",
86            [
87                'abusefilter-log-hide-selected',
88                $this->getLanguage()->formatNum( count( $this->hideIDs ) )
89            ]
90        );
91        $output->addHTML( Html::rawElement( 'ul', [ 'class' => 'plainlinks' ], $pager->getBody() ) );
92
93        $hideReasonsOther = $this->msg( 'revdelete-reasonotherlist' )->text();
94        $hideReasons = $this->msg( 'revdelete-reason-dropdown-suppress' )->inContentLanguage()->text();
95        $hideReasons = Html::listDropdownOptions( $hideReasons, [ 'other' => $hideReasonsOther ] );
96
97        $formInfo = [
98            'showorhide' => [
99                'type' => 'radio',
100                'label-message' => 'abusefilter-log-hide-set-visibility',
101                'options-messages' => [
102                    'abusefilter-log-hide-show' => 'show',
103                    'abusefilter-log-hide-hide' => 'hide'
104                ],
105                'default' => 'hide',
106                'flatlist' => true
107            ],
108            'dropdownreason' => [
109                'type' => 'select',
110                'options' => $hideReasons,
111                'label-message' => 'abusefilter-log-hide-reason'
112            ],
113            'reason' => [
114                'type' => 'text',
115                'label-message' => 'abusefilter-log-hide-reason-other',
116            ],
117        ];
118
119        $actionURL = $this->getTitle( 'hide' )->getLocalURL( [ 'hideids' => array_fill_keys( $this->hideIDs, 1 ) ] );
120        HTMLForm::factory( 'ooui', $formInfo, $this->getContext() )
121            ->setAction( $actionURL )
122            ->setWrapperLegend( $this->msg( 'abusefilter-log-hide-legend' )->text() )
123            ->setSubmitCallback( [ $this, 'saveHideForm' ] )
124            ->showAlways();
125
126        // Show suppress log for this entry. Hack: since every suppression is performed on a
127        // totally different page (i.e. Special:AbuseLog/xxx), we use showLogExtract without
128        // specifying a title and then adding it in conds.
129        // This isn't shown if the request was posted because we update visibility in a DeferredUpdate, so it would
130        // display outdated info that might confuse the user.
131        // TODO Can we improve this somehow?
132        if ( !$this->getRequest()->wasPosted() ) {
133            $suppressLogPage = new LogPage( 'suppress' );
134            $output->addHTML( "<h2>" . $suppressLogPage->getName()->escaped() . "</h2>\n" );
135            $searchTitles = [];
136            foreach ( $this->hideIDs as $id ) {
137                $searchTitles[] = $this->getTitle( (string)$id )->getDBkey();
138            }
139            $conds = [ 'log_namespace' => NS_SPECIAL, 'log_title' => $searchTitles ];
140            LogEventsList::showLogExtract( $output, 'suppress', '', '', [ 'conds' => $conds ] );
141        }
142    }
143
144    /**
145     * Process the hide form after submission. This performs the actual visibility update. Used as callback by HTMLForm
146     *
147     * @param array $fields
148     * @return bool|array True on success, array of error message keys otherwise
149     */
150    public function saveHideForm( array $fields ) {
151        // Determine which rows actually have to be changed
152        $dbw = $this->lbFactory->getPrimaryDatabase();
153        $newValue = $fields['showorhide'] === 'hide' ? 1 : 0;
154        $actualIDs = $dbw->newSelectQueryBuilder()
155            ->select( 'afl_id' )
156            ->from( 'abuse_filter_log' )
157            ->where( [
158                'afl_id' => $this->hideIDs,
159                $dbw->expr( 'afl_deleted', '!=', $newValue ),
160            ] )
161            ->caller( __METHOD__ )
162            ->fetchFieldValues();
163        if ( !count( $actualIDs ) ) {
164            return [ 'abusefilter-log-hide-no-change' ];
165        }
166
167        $dbw->newUpdateQueryBuilder()
168            ->update( 'abuse_filter_log' )
169            ->set( [ 'afl_deleted' => $newValue ] )
170            ->where( [ 'afl_id' => $actualIDs ] )
171            ->caller( __METHOD__ )
172            ->execute();
173
174        // Log in a DeferredUpdates to avoid potential flood
175        DeferredUpdates::addCallableUpdate( function () use ( $fields, $actualIDs ) {
176            $reason = $fields['dropdownreason'];
177            if ( $reason === 'other' ) {
178                $reason = $fields['reason'];
179            } elseif ( $fields['reason'] !== '' ) {
180                $reason .=
181                    $this->msg( 'colon-separator' )->inContentLanguage()->text() . $fields['reason'];
182            }
183
184            $action = $fields['showorhide'] === 'hide' ? 'hide-afl' : 'unhide-afl';
185            foreach ( $actualIDs as $logid ) {
186                $logEntry = new ManualLogEntry( 'suppress', $action );
187                $logEntry->setPerformer( $this->getUser() );
188                $logEntry->setTarget( $this->getTitle( $logid ) );
189                $logEntry->setComment( $reason );
190                $logEntry->insert();
191            }
192        } );
193
194        $count = count( $actualIDs );
195        $this->getOutput()->addModuleStyles( 'mediawiki.codex.messagebox.styles' );
196        $this->getOutput()->prependHTML(
197            Html::successBox(
198                $this->msg( 'abusefilter-log-hide-done' )->params(
199                    $this->getLanguage()->formatNum( $count ),
200                    // Messages used: abusefilter-log-hide-done-hide, abusefilter-log-hide-done-show
201                    $this->msg( 'abusefilter-log-hide-done-' . $fields['showorhide'] )->numParams( $count )->text()
202                )->escaped()
203            )
204        );
205
206        return true;
207    }
208
209}