Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
74.49% covered (warning)
74.49%
73 / 98
37.50% covered (danger)
37.50%
3 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiPurge
75.26% covered (warning)
75.26%
73 / 97
37.50% covered (danger)
37.50%
3 / 8
29.33
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 execute
81.69% covered (warning)
81.69%
58 / 71
0.00% covered (danger)
0.00%
0 / 1
15.20
 getPageSet
100.00% covered (success)
100.00%
2 / 2
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
 mustBePosted
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAllowedParams
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
2.00
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 8
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 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\Api;
22
23use MediaWiki\Deferred\DeferredUpdates;
24use MediaWiki\Logger\LoggerFactory;
25use MediaWiki\Page\WikiPageFactory;
26use MediaWiki\Permissions\PermissionStatus;
27use MediaWiki\Status\Status;
28use MediaWiki\Title\Title;
29use MediaWiki\Title\TitleFormatter;
30
31/**
32 * API interface for page purging
33 * @ingroup API
34 */
35class ApiPurge extends ApiBase {
36    /** @var ApiPageSet|null */
37    private $mPageSet = null;
38
39    private WikiPageFactory $wikiPageFactory;
40    private TitleFormatter $titleFormatter;
41
42    public function __construct(
43        ApiMain $mainModule,
44        string $moduleName,
45        WikiPageFactory $wikiPageFactory,
46        TitleFormatter $titleFormatter
47    ) {
48        parent::__construct( $mainModule, $moduleName );
49        $this->wikiPageFactory = $wikiPageFactory;
50        $this->titleFormatter = $titleFormatter;
51    }
52
53    /**
54     * Purges the cache of a page
55     */
56    public function execute() {
57        $authority = $this->getAuthority();
58
59        // Fail early if the user is sitewide blocked.
60        $block = $authority->getBlock();
61        if ( $block && $block->isSitewide() ) {
62            $this->dieBlocked( $block );
63        }
64
65        $params = $this->extractRequestParams();
66
67        $continuationManager = new ApiContinuationManager( $this, [], [] );
68        $this->setContinuationManager( $continuationManager );
69
70        $forceLinkUpdate = $params['forcelinkupdate'];
71        $forceRecursiveLinkUpdate = $params['forcerecursivelinkupdate'];
72        $pageSet = $this->getPageSet();
73        $pageSet->execute();
74
75        $result = $pageSet->getInvalidTitlesAndRevisions();
76        $userName = $authority->getUser()->getName();
77        $now = wfTimestampNow();
78
79        foreach ( $pageSet->getGoodPages() as $pageIdentity ) {
80            $title = $this->titleFormatter->getPrefixedText( $pageIdentity );
81            $r = [
82                'ns' => $pageIdentity->getNamespace(),
83                'title' => $title,
84            ];
85            $page = $this->wikiPageFactory->newFromTitle( $pageIdentity );
86
87            $purgeAuthStatus = PermissionStatus::newEmpty();
88            if ( $authority->authorizeAction( 'purge', $purgeAuthStatus ) ) {
89                // Directly purge and skip the UI part of purge()
90                $page->doPurge();
91                $r['purged'] = true;
92            } else {
93                if ( $purgeAuthStatus->isRateLimitExceeded() ) {
94                    $this->addWarning( 'apierror-ratelimited' );
95                } else {
96                    $this->addWarning( Status::wrap( $purgeAuthStatus )->getMessage() );
97                }
98            }
99
100            if ( $forceLinkUpdate || $forceRecursiveLinkUpdate ) {
101                $linkpurgeAuthStatus = PermissionStatus::newEmpty();
102                if ( $authority->authorizeAction( 'linkpurge', $linkpurgeAuthStatus ) ) {
103                    # Logging to better see expensive usage patterns
104                    if ( $forceRecursiveLinkUpdate ) {
105                        LoggerFactory::getInstance( 'RecursiveLinkPurge' )->info(
106                            "Recursive link purge enqueued for {title}",
107                            [
108                                'user' => $userName,
109                                'title' => $title
110                            ]
111                        );
112                    }
113
114                    $page->updateParserCache( [
115                        'causeAction' => 'api-purge',
116                        'causeAgent' => $userName,
117                    ] );
118                    $page->doSecondaryDataUpdates( [
119                        'recursive' => $forceRecursiveLinkUpdate,
120                        'causeAction' => 'api-purge',
121                        'causeAgent' => $userName,
122                        'defer' => DeferredUpdates::PRESEND,
123                        'freshness' => $now,
124                    ] );
125                    $r['linkupdate'] = true;
126                } else {
127                    if ( $linkpurgeAuthStatus->isRateLimitExceeded() ) {
128                        $this->addWarning( 'apierror-ratelimited' );
129                        $forceLinkUpdate = false;
130                        $forceRecursiveLinkUpdate = false;
131                    } else {
132                        $this->addWarning( Status::wrap( $linkpurgeAuthStatus )->getMessage() );
133                    }
134                }
135            }
136
137            $result[] = $r;
138        }
139        $apiResult = $this->getResult();
140        ApiResult::setIndexedTagName( $result, 'page' );
141        $apiResult->addValue( null, $this->getModuleName(), $result );
142
143        $values = $pageSet->getNormalizedTitlesAsResult( $apiResult );
144        if ( $values ) {
145            $apiResult->addValue( null, 'normalized', $values );
146        }
147        $values = $pageSet->getConvertedTitlesAsResult( $apiResult );
148        if ( $values ) {
149            $apiResult->addValue( null, 'converted', $values );
150        }
151        $values = $pageSet->getRedirectTitlesAsResult( $apiResult );
152        if ( $values ) {
153            $apiResult->addValue( null, 'redirects', $values );
154        }
155
156        $this->setContinuationManager( null );
157        $continuationManager->setContinuationIntoResult( $apiResult );
158    }
159
160    /**
161     * Get a cached instance of an ApiPageSet object
162     * @return ApiPageSet
163     */
164    private function getPageSet() {
165        $this->mPageSet ??= new ApiPageSet( $this );
166
167        return $this->mPageSet;
168    }
169
170    public function isWriteMode() {
171        return true;
172    }
173
174    public function mustBePosted() {
175        return true;
176    }
177
178    public function getAllowedParams( $flags = 0 ) {
179        $result = [
180            'forcelinkupdate' => false,
181            'forcerecursivelinkupdate' => false,
182            'continue' => [
183                ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
184            ],
185        ];
186        if ( $flags ) {
187            $result += $this->getPageSet()->getFinalParams( $flags );
188        }
189
190        return $result;
191    }
192
193    protected function getExamplesMessages() {
194        $title = Title::newMainPage()->getPrefixedText();
195        $mp = rawurlencode( $title );
196
197        return [
198            "action=purge&titles={$mp}|API"
199                => 'apihelp-purge-example-simple',
200            'action=purge&generator=allpages&gapnamespace=0&gaplimit=10'
201                => 'apihelp-purge-example-generator',
202        ];
203    }
204
205    public function getHelpUrls() {
206        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Purge';
207    }
208}
209
210/** @deprecated class alias since 1.43 */
211class_alias( ApiPurge::class, 'ApiPurge' );