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