Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
73.60% covered (warning)
73.60%
92 / 125
62.50% covered (warning)
62.50%
5 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiProtect
74.19% covered (warning)
74.19%
92 / 124
62.50% covered (warning)
62.50%
5 / 8
39.53
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 execute
75.00% covered (warning)
75.00%
54 / 72
0.00% covered (danger)
0.00%
0 / 1
26.25
 mustBePosted
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isWriteMode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAllowedParams
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
1
 needsToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 getHelpUrls
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Copyright © 2007 Roan Kattouw <roan.kattouw@gmail.com>
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 */
8
9namespace MediaWiki\Api;
10
11use InvalidArgumentException;
12use MediaWiki\ChangeTags\ChangeTags;
13use MediaWiki\MainConfigNames;
14use MediaWiki\Permissions\RestrictionStore;
15use MediaWiki\Title\Title;
16use MediaWiki\User\Options\UserOptionsLookup;
17use MediaWiki\Utils\MWTimestamp;
18use MediaWiki\Watchlist\WatchedItemStoreInterface;
19use MediaWiki\Watchlist\WatchlistManager;
20use Wikimedia\ParamValidator\ParamValidator;
21use Wikimedia\ParamValidator\TypeDef\ExpiryDef;
22use Wikimedia\Timestamp\TimestampFormat as TS;
23
24/**
25 * @ingroup API
26 */
27class ApiProtect extends ApiBase {
28
29    use ApiWatchlistTrait;
30
31    private RestrictionStore $restrictionStore;
32
33    public function __construct(
34        ApiMain $mainModule,
35        string $moduleName,
36        WatchlistManager $watchlistManager,
37        WatchedItemStoreInterface $watchedItemStore,
38        UserOptionsLookup $userOptionsLookup,
39        RestrictionStore $restrictionStore
40    ) {
41        parent::__construct( $mainModule, $moduleName );
42        $this->restrictionStore = $restrictionStore;
43
44        // Variables needed in ApiWatchlistTrait trait
45        $this->watchlistExpiryEnabled = $this->getConfig()->get( MainConfigNames::WatchlistExpiry );
46        $this->watchlistMaxDuration =
47            $this->getConfig()->get( MainConfigNames::WatchlistExpiryMaxDuration );
48        $this->watchlistManager = $watchlistManager;
49        $this->watchedItemStore = $watchedItemStore;
50        $this->userOptionsLookup = $userOptionsLookup;
51    }
52
53    public function execute() {
54        $params = $this->extractRequestParams();
55
56        $pageObj = $this->getTitleOrPageId( $params, 'fromdbmaster' );
57        $titleObj = $pageObj->getTitle();
58        $this->getErrorFormatter()->setContextTitle( $titleObj );
59
60        $this->checkTitleUserPermissions( $titleObj, 'protect' );
61
62        $user = $this->getUser();
63        $tags = $params['tags'];
64
65        // Check if user can add tags
66        if ( $tags !== null ) {
67            $ableToTag = ChangeTags::canAddTagsAccompanyingChange( $tags, $this->getAuthority() );
68            if ( !$ableToTag->isOK() ) {
69                $this->dieStatus( $ableToTag );
70            }
71        }
72
73        $expiry = (array)$params['expiry'];
74        if ( count( $expiry ) != count( $params['protections'] ) ) {
75            if ( count( $expiry ) == 1 ) {
76                $expiry = array_fill( 0, count( $params['protections'] ), $expiry[0] );
77            } else {
78                $this->dieWithError( [
79                    'apierror-toofewexpiries',
80                    count( $expiry ),
81                    count( $params['protections'] )
82                ] );
83            }
84        }
85
86        $restrictionTypes = $this->restrictionStore->listApplicableRestrictionTypes( $titleObj );
87        $levels = $this->getPermissionManager()->getNamespaceRestrictionLevels(
88            $titleObj->getNamespace(),
89            $user
90        );
91
92        $protections = [];
93        $expiries = [];
94        $resultProtections = [];
95        foreach ( $params['protections'] as $i => $prot ) {
96            $p = explode( '=', $prot );
97            $protections[$p[0]] = ( $p[1] == 'all' ? '' : $p[1] );
98
99            if ( $titleObj->exists() && $p[0] == 'create' ) {
100                $this->dieWithError( 'apierror-create-titleexists' );
101            }
102            if ( !$titleObj->exists() && $p[0] != 'create' ) {
103                $this->dieWithError( 'apierror-missingtitle-createonly' );
104            }
105
106            if ( !in_array( $p[0], $restrictionTypes ) && $p[0] != 'create' ) {
107                $this->dieWithError( [ 'apierror-protect-invalidaction', wfEscapeWikiText( $p[0] ) ] );
108            }
109            if ( !in_array( $p[1], $levels ) && $p[1] != 'all' ) {
110                $this->dieWithError( [ 'apierror-protect-invalidlevel', wfEscapeWikiText( $p[1] ) ] );
111            }
112
113            try {
114                $expiries[$p[0]] = ExpiryDef::normalizeExpiry( $expiry[$i], TS::MW );
115            } catch ( InvalidArgumentException ) {
116                $this->dieWithError( [ 'apierror-invalidexpiry', wfEscapeWikiText( $expiry[$i] ) ] );
117            }
118            if ( $expiries[$p[0]] < MWTimestamp::now( TS::MW ) ) {
119                $this->dieWithError( [ 'apierror-pastexpiry', wfEscapeWikiText( $expiry[$i] ) ] );
120            }
121
122            $resultProtections[] = [
123                $p[0] => $protections[$p[0]],
124                'expiry' => ApiResult::formatExpiry( $expiries[$p[0]], 'infinite' ),
125            ];
126        }
127
128        $cascade = $params['cascade'];
129
130        $watch = $params['watch'] ? 'watch' : $params['watchlist'];
131        $watchlistExpiry = $this->getExpiryFromParams( $params, $titleObj, $user );
132        $this->setWatch( $watch, $titleObj, $user, 'watchdefault', $watchlistExpiry );
133
134        $status = $pageObj->doUpdateRestrictions(
135            $protections,
136            $expiries,
137            $cascade,
138            $params['reason'],
139            $user,
140            $tags ?? []
141        );
142
143        if ( !$status->isOK() ) {
144            $this->dieStatus( $status );
145        }
146        $res = [
147            'title' => $titleObj->getPrefixedText(),
148            'reason' => $params['reason']
149        ];
150        if ( $cascade ) {
151            $res['cascade'] = true;
152        }
153        $res['protections'] = $resultProtections;
154        $result = $this->getResult();
155        ApiResult::setIndexedTagName( $res['protections'], 'protection' );
156        $result->addValue( null, $this->getModuleName(), $res );
157    }
158
159    /** @inheritDoc */
160    public function mustBePosted() {
161        return true;
162    }
163
164    /** @inheritDoc */
165    public function isWriteMode() {
166        return true;
167    }
168
169    /** @inheritDoc */
170    public function getAllowedParams() {
171        return [
172            'title' => [
173                ParamValidator::PARAM_TYPE => 'string',
174            ],
175            'pageid' => [
176                ParamValidator::PARAM_TYPE => 'integer',
177            ],
178            'protections' => [
179                ParamValidator::PARAM_ISMULTI => true,
180                ParamValidator::PARAM_REQUIRED => true,
181            ],
182            'expiry' => [
183                ParamValidator::PARAM_ISMULTI => true,
184                ParamValidator::PARAM_ALLOW_DUPLICATES => true,
185                ParamValidator::PARAM_DEFAULT => 'infinite',
186            ],
187            'reason' => '',
188            'tags' => [
189                ParamValidator::PARAM_TYPE => 'tags',
190                ParamValidator::PARAM_ISMULTI => true,
191            ],
192            'cascade' => false,
193            'watch' => [
194                ParamValidator::PARAM_DEFAULT => false,
195                ParamValidator::PARAM_DEPRECATED => true,
196            ],
197        ] + $this->getWatchlistParams();
198    }
199
200    /** @inheritDoc */
201    public function needsToken() {
202        return 'csrf';
203    }
204
205    /** @inheritDoc */
206    protected function getExamplesMessages() {
207        $title = Title::newMainPage()->getPrefixedText();
208        $mp = rawurlencode( $title );
209
210        return [
211            "action=protect&title={$mp}&token=123ABC&" .
212                'protections=edit=sysop|move=sysop&cascade=&expiry=20070901163000|never'
213                => 'apihelp-protect-example-protect',
214            "action=protect&title={$mp}&token=123ABC&" .
215                'protections=edit=all|move=all&reason=Lifting%20restrictions'
216                => 'apihelp-protect-example-unprotect',
217            "action=protect&title={$mp}&token=123ABC&" .
218                'protections=&reason=Lifting%20restrictions'
219                => 'apihelp-protect-example-unprotect2',
220        ];
221    }
222
223    /** @inheritDoc */
224    public function getHelpUrls() {
225        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Protect';
226    }
227}
228
229/** @deprecated class alias since 1.43 */
230class_alias( ApiProtect::class, 'ApiProtect' );