Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.00% covered (warning)
75.00%
66 / 88
62.50% covered (warning)
62.50%
5 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiUndelete
75.86% covered (warning)
75.86%
66 / 87
62.50% covered (warning)
62.50%
5 / 8
30.44
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 execute
76.60% covered (warning)
76.60%
36 / 47
0.00% covered (danger)
0.00%
0 / 1
19.28
 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%
20 / 20
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 / 9
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 MediaWiki\MainConfigNames;
12use MediaWiki\Page\UndeletePage;
13use MediaWiki\Page\UndeletePageFactory;
14use MediaWiki\Page\WikiPageFactory;
15use MediaWiki\Title\Title;
16use MediaWiki\User\Options\UserOptionsLookup;
17use MediaWiki\Watchlist\WatchedItemStoreInterface;
18use MediaWiki\Watchlist\WatchlistManager;
19use Wikimedia\ParamValidator\ParamValidator;
20use Wikimedia\Rdbms\IDBAccessObject;
21use Wikimedia\Timestamp\TimestampFormat as TS;
22
23/**
24 * @ingroup API
25 */
26class ApiUndelete extends ApiBase {
27
28    use ApiWatchlistTrait;
29
30    public function __construct(
31        ApiMain $mainModule,
32        string $moduleName,
33        WatchlistManager $watchlistManager,
34        WatchedItemStoreInterface $watchedItemStore,
35        UserOptionsLookup $userOptionsLookup,
36        private readonly UndeletePageFactory $undeletePageFactory,
37        private readonly WikiPageFactory $wikiPageFactory,
38    ) {
39        parent::__construct( $mainModule, $moduleName );
40
41        // Variables needed in ApiWatchlistTrait trait
42        $this->watchlistExpiryEnabled = $this->getConfig()->get( MainConfigNames::WatchlistExpiry );
43        $this->watchlistMaxDuration =
44            $this->getConfig()->get( MainConfigNames::WatchlistExpiryMaxDuration );
45        $this->watchlistManager = $watchlistManager;
46        $this->watchedItemStore = $watchedItemStore;
47        $this->userOptionsLookup = $userOptionsLookup;
48    }
49
50    public function execute() {
51        $this->useTransactionalTimeLimit();
52
53        $params = $this->extractRequestParams();
54
55        $user = $this->getUser();
56        $block = $user->getBlock( IDBAccessObject::READ_LATEST );
57        if ( $block && $block->isSitewide() ) {
58            $this->dieBlocked( $block );
59        }
60
61        $titleObj = Title::newFromText( $params['title'] );
62        if ( !$titleObj || $titleObj->isExternal() ) {
63            $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
64        }
65        if ( !$titleObj->canExist() ) {
66            $this->dieWithError( 'apierror-pagecannotexist' );
67        }
68
69        // Convert timestamps
70        if ( !isset( $params['timestamps'] ) ) {
71            $params['timestamps'] = [];
72        }
73        if ( !is_array( $params['timestamps'] ) ) {
74            $params['timestamps'] = [ $params['timestamps'] ];
75        }
76        foreach ( $params['timestamps'] as $i => $ts ) {
77            $params['timestamps'][$i] = wfTimestamp( TS::MW, $ts );
78        }
79
80        $undeletePage = $this->undeletePageFactory->newUndeletePage(
81                $this->wikiPageFactory->newFromTitle( $titleObj ),
82                $this->getAuthority()
83            )
84            ->setUndeleteOnlyTimestamps( $params['timestamps'] ?? [] )
85            ->setUndeleteOnlyFileVersions( $params['fileids'] ?: [] )
86            ->setTags( $params['tags'] ?: [] );
87
88        if ( $params['undeletetalk'] ) {
89            $undeletePage->setUndeleteAssociatedTalk( true );
90        }
91
92        $status = $undeletePage->undeleteIfAllowed( $params['reason'] );
93        if ( $status->isOK() ) {
94            // in case there are warnings
95            $this->addMessagesFromStatus( $status );
96        } else {
97            $this->dieStatus( $status );
98        }
99
100        $restoredRevs = $status->getValue()[UndeletePage::REVISIONS_RESTORED];
101        $restoredFiles = $status->getValue()[UndeletePage::FILES_RESTORED];
102
103        if ( $restoredRevs === 0 && $restoredFiles === 0 ) {
104            // BC for code that predates UndeletePage
105            $this->dieWithError( 'apierror-cantundelete' );
106        }
107
108        if ( $restoredFiles ) {
109            $this->getHookRunner()->onFileUndeleteComplete(
110                $titleObj, $params['fileids'],
111                $this->getUser(), $params['reason'] );
112        }
113
114        $watchlistExpiry = $this->getExpiryFromParams( $params, $titleObj, $user );
115        $this->setWatch( $params['watchlist'], $titleObj, $user, null, $watchlistExpiry );
116
117        $info = [
118            'title' => $titleObj->getPrefixedText(),
119            'revisions' => $restoredRevs,
120            'fileversions' => $restoredFiles,
121            'reason' => $params['reason']
122        ];
123        $this->getResult()->addValue( null, $this->getModuleName(), $info );
124    }
125
126    /** @inheritDoc */
127    public function mustBePosted() {
128        return true;
129    }
130
131    /** @inheritDoc */
132    public function isWriteMode() {
133        return true;
134    }
135
136    /** @inheritDoc */
137    public function getAllowedParams() {
138        return [
139            'title' => [
140                ParamValidator::PARAM_TYPE => 'string',
141                ParamValidator::PARAM_REQUIRED => true
142            ],
143            'reason' => '',
144            'tags' => [
145                ParamValidator::PARAM_TYPE => 'tags',
146                ParamValidator::PARAM_ISMULTI => true,
147            ],
148            'timestamps' => [
149                ParamValidator::PARAM_TYPE => 'timestamp',
150                ParamValidator::PARAM_ISMULTI => true,
151            ],
152            'fileids' => [
153                ParamValidator::PARAM_TYPE => 'integer',
154                ParamValidator::PARAM_ISMULTI => true,
155            ],
156            'undeletetalk' => false,
157        ] + $this->getWatchlistParams();
158    }
159
160    /** @inheritDoc */
161    public function needsToken() {
162        return 'csrf';
163    }
164
165    /** @inheritDoc */
166    protected function getExamplesMessages() {
167        $title = Title::newMainPage()->getPrefixedText();
168        $mp = rawurlencode( $title );
169
170        return [
171            "action=undelete&title={$mp}&token=123ABC&reason=Restoring%20{$mp}"
172                => 'apihelp-undelete-example-page',
173            "action=undelete&title={$mp}&token=123ABC" .
174                '&timestamps=2007-07-03T22:00:45Z|2007-07-02T19:48:56Z'
175                => 'apihelp-undelete-example-revisions',
176        ];
177    }
178
179    /** @inheritDoc */
180    public function getHelpUrls() {
181        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Undelete';
182    }
183}
184
185/** @deprecated class alias since 1.43 */
186class_alias( ApiUndelete::class, 'ApiUndelete' );