Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
56.25% covered (warning)
56.25%
54 / 96
37.50% covered (danger)
37.50%
3 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiPurge
56.25% covered (warning)
56.25%
54 / 96
37.50% covered (danger)
37.50%
3 / 8
62.53
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
55.71% covered (warning)
55.71%
39 / 70
0.00% covered (danger)
0.00%
0 / 1
31.02
 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
21use MediaWiki\Deferred\DeferredUpdates;
22use MediaWiki\Logger\LoggerFactory;
23use MediaWiki\Page\WikiPageFactory;
24use MediaWiki\Permissions\PermissionStatus;
25use MediaWiki\Status\Status;
26use MediaWiki\Title\Title;
27use MediaWiki\Title\TitleFormatter;
28
29/**
30 * API interface for page purging
31 * @ingroup API
32 */
33class ApiPurge extends ApiBase {
34    /** @var ApiPageSet|null */
35    private $mPageSet = null;
36
37    private WikiPageFactory $wikiPageFactory;
38    private TitleFormatter $titleFormatter;
39
40    /**
41     * @param ApiMain $mainModule
42     * @param string $moduleName
43     * @param WikiPageFactory $wikiPageFactory
44     * @param TitleFormatter $titleFormatter
45     */
46    public function __construct(
47        ApiMain $mainModule,
48        $moduleName,
49        WikiPageFactory $wikiPageFactory,
50        TitleFormatter $titleFormatter
51    ) {
52        parent::__construct( $mainModule, $moduleName );
53        $this->wikiPageFactory = $wikiPageFactory;
54        $this->titleFormatter = $titleFormatter;
55    }
56
57    /**
58     * Purges the cache of a page
59     */
60    public function execute() {
61        $authority = $this->getAuthority();
62
63        // Fail early if the user is sitewide blocked.
64        $block = $authority->getBlock();
65        if ( $block && $block->isSitewide() ) {
66            $this->dieBlocked( $block );
67        }
68
69        $params = $this->extractRequestParams();
70
71        $continuationManager = new ApiContinuationManager( $this, [], [] );
72        $this->setContinuationManager( $continuationManager );
73
74        $forceLinkUpdate = $params['forcelinkupdate'];
75        $forceRecursiveLinkUpdate = $params['forcerecursivelinkupdate'];
76        $pageSet = $this->getPageSet();
77        $pageSet->execute();
78
79        $result = $pageSet->getInvalidTitlesAndRevisions();
80        $userName = $authority->getUser()->getName();
81        $now = wfTimestampNow();
82
83        foreach ( $pageSet->getGoodPages() as $pageIdentity ) {
84            $title = $this->titleFormatter->getPrefixedText( $pageIdentity );
85            $r = [
86                'ns' => $pageIdentity->getNamespace(),
87                'title' => $title,
88            ];
89            $page = $this->wikiPageFactory->newFromTitle( $pageIdentity );
90
91            $authStatus = PermissionStatus::newEmpty();
92            if ( $authority->authorizeAction( 'purge', $authStatus ) ) {
93                // Directly purge and skip the UI part of purge()
94                $page->doPurge();
95                $r['purged'] = true;
96            } else {
97                if ( $authStatus->isRateLimitExceeded() ) {
98                    $this->addWarning( 'apierror-ratelimited' );
99                } else {
100                    $this->addWarning( Status::wrap( $authStatus )->getMessage() );
101                }
102            }
103
104            if ( $forceLinkUpdate || $forceRecursiveLinkUpdate ) {
105                if ( $authority->authorizeAction( 'linkpurge', $authStatus ) ) {
106                    # Logging to better see expensive usage patterns
107                    if ( $forceRecursiveLinkUpdate ) {
108                        LoggerFactory::getInstance( 'RecursiveLinkPurge' )->info(
109                            "Recursive link purge enqueued for {title}",
110                            [
111                                'user' => $userName,
112                                'title' => $title
113                            ]
114                        );
115                    }
116
117                    $page->updateParserCache( [
118                        'causeAction' => 'api-purge',
119                        'causeAgent' => $userName,
120                    ] );
121                    $page->doSecondaryDataUpdates( [
122                        'recursive' => $forceRecursiveLinkUpdate,
123                        'causeAction' => 'api-purge',
124                        'causeAgent' => $userName,
125                        'defer' => DeferredUpdates::PRESEND,
126                        'freshness' => $now,
127                    ] );
128                    $r['linkupdate'] = true;
129                } else {
130                    if ( $authStatus->isRateLimitExceeded() ) {
131                        $this->addWarning( 'apierror-ratelimited' );
132                        $forceLinkUpdate = false;
133                        $forceRecursiveLinkUpdate = false;
134                    } else {
135                        $this->addWarning( Status::wrap( $authStatus )->getMessage() );
136                    }
137                }
138            }
139
140            $result[] = $r;
141        }
142        $apiResult = $this->getResult();
143        ApiResult::setIndexedTagName( $result, 'page' );
144        $apiResult->addValue( null, $this->getModuleName(), $result );
145
146        $values = $pageSet->getNormalizedTitlesAsResult( $apiResult );
147        if ( $values ) {
148            $apiResult->addValue( null, 'normalized', $values );
149        }
150        $values = $pageSet->getConvertedTitlesAsResult( $apiResult );
151        if ( $values ) {
152            $apiResult->addValue( null, 'converted', $values );
153        }
154        $values = $pageSet->getRedirectTitlesAsResult( $apiResult );
155        if ( $values ) {
156            $apiResult->addValue( null, 'redirects', $values );
157        }
158
159        $this->setContinuationManager( null );
160        $continuationManager->setContinuationIntoResult( $apiResult );
161    }
162
163    /**
164     * Get a cached instance of an ApiPageSet object
165     * @return ApiPageSet
166     */
167    private function getPageSet() {
168        $this->mPageSet ??= new ApiPageSet( $this );
169
170        return $this->mPageSet;
171    }
172
173    public function isWriteMode() {
174        return true;
175    }
176
177    public function mustBePosted() {
178        return true;
179    }
180
181    public function getAllowedParams( $flags = 0 ) {
182        $result = [
183            'forcelinkupdate' => false,
184            'forcerecursivelinkupdate' => false,
185            'continue' => [
186                ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
187            ],
188        ];
189        if ( $flags ) {
190            $result += $this->getPageSet()->getFinalParams( $flags );
191        }
192
193        return $result;
194    }
195
196    protected function getExamplesMessages() {
197        $title = Title::newMainPage()->getPrefixedText();
198        $mp = rawurlencode( $title );
199
200        return [
201            "action=purge&titles={$mp}|API"
202                => 'apihelp-purge-example-simple',
203            'action=purge&generator=allpages&gapnamespace=0&gaplimit=10'
204                => 'apihelp-purge-example-generator',
205        ];
206    }
207
208    public function getHelpUrls() {
209        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Purge';
210    }
211}