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