Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 118
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiImageRotate
0.00% covered (danger)
0.00%
0 / 117
0.00% covered (danger)
0.00%
0 / 9
506
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 84
0.00% covered (danger)
0.00%
0 / 1
182
 getPageSet
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 mustBePosted
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isWriteMode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAllowedParams
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
6
 needsToken
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 7
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 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\Api;
8
9use MediaWiki\ChangeTags\ChangeTags;
10use MediaWiki\FileRepo\RepoGroup;
11use MediaWiki\Status\Status;
12use MediaWiki\Title\TitleFactory;
13use Wikimedia\FileBackend\FSFile\TempFSFileFactory;
14use Wikimedia\ParamValidator\ParamValidator;
15
16/**
17 * @ingroup API
18 */
19class ApiImageRotate extends ApiBase {
20    /** @var ApiPageSet|null */
21    private $mPageSet = null;
22
23    private RepoGroup $repoGroup;
24    private TempFSFileFactory $tempFSFileFactory;
25    private TitleFactory $titleFactory;
26
27    public function __construct(
28        ApiMain $mainModule,
29        string $moduleName,
30        RepoGroup $repoGroup,
31        TempFSFileFactory $tempFSFileFactory,
32        TitleFactory $titleFactory
33    ) {
34        parent::__construct( $mainModule, $moduleName );
35        $this->repoGroup = $repoGroup;
36        $this->tempFSFileFactory = $tempFSFileFactory;
37        $this->titleFactory = $titleFactory;
38    }
39
40    public function execute() {
41        $this->useTransactionalTimeLimit();
42
43        $params = $this->extractRequestParams();
44        $rotation = $params['rotation'];
45
46        $continuationManager = new ApiContinuationManager( $this, [], [] );
47        $this->setContinuationManager( $continuationManager );
48
49        $pageSet = $this->getPageSet();
50        $pageSet->execute();
51
52        $result = $pageSet->getInvalidTitlesAndRevisions( [
53            'invalidTitles', 'special', 'missingIds', 'missingRevIds', 'interwikiTitles',
54        ] );
55
56        // Check if user can add tags
57        if ( $params['tags'] ) {
58            $ableToTag = ChangeTags::canAddTagsAccompanyingChange( $params['tags'], $this->getAuthority() );
59            if ( !$ableToTag->isOK() ) {
60                $this->dieStatus( $ableToTag );
61            }
62        }
63
64        foreach ( $pageSet->getPages() as $page ) {
65            $title = $this->titleFactory->newFromPageIdentity( $page );
66            $r = [];
67            $r['id'] = $title->getArticleID();
68            ApiQueryBase::addTitleInfo( $r, $title );
69            if ( !$title->exists() ) {
70                $r['missing'] = true;
71                if ( $title->isKnown() ) {
72                    $r['known'] = true;
73                }
74            }
75
76            $file = $this->repoGroup->findFile( $title, [ 'latest' => true ] );
77            if ( !$file ) {
78                $r['result'] = 'Failure';
79                $r['errors'] = $this->getErrorFormatter()->arrayFromStatus(
80                    Status::newFatal( 'apierror-filedoesnotexist' )
81                );
82                $result[] = $r;
83                continue;
84            }
85            $handler = $file->getHandler();
86            if ( !$handler || !$handler->canRotate() ) {
87                $r['result'] = 'Failure';
88                $r['errors'] = $this->getErrorFormatter()->arrayFromStatus(
89                    Status::newFatal( 'apierror-filetypecannotberotated' )
90                );
91                $result[] = $r;
92                continue;
93            }
94
95            // Check whether we're allowed to rotate this file
96            $this->checkTitleUserPermissions( $file->getTitle(), [ 'edit', 'upload' ] );
97
98            $srcPath = $file->getLocalRefPath();
99            if ( $srcPath === false ) {
100                $r['result'] = 'Failure';
101                $r['errors'] = $this->getErrorFormatter()->arrayFromStatus(
102                    Status::newFatal( 'apierror-filenopath' )
103                );
104                $result[] = $r;
105                continue;
106            }
107            $ext = strtolower( pathinfo( "$srcPath", PATHINFO_EXTENSION ) );
108            $tmpFile = $this->tempFSFileFactory->newTempFSFile( 'rotate_', $ext );
109            $dstPath = $tmpFile->getPath();
110            // @phan-suppress-next-line PhanUndeclaredMethod
111            $err = $handler->rotate( $file, [
112                'srcPath' => $srcPath,
113                'dstPath' => $dstPath,
114                'rotation' => $rotation
115            ] );
116            if ( !$err ) {
117                $comment = $this->msg(
118                    'rotate-comment'
119                )->numParams( $rotation )->inContentLanguage()->text();
120                // @phan-suppress-next-line PhanUndeclaredMethod
121                $status = $file->upload(
122                    $dstPath,
123                    $comment,
124                    $comment,
125                    0,
126                    false,
127                    false,
128                    $this->getAuthority(),
129                    $params['tags'] ?: []
130                );
131                if ( $status->isGood() ) {
132                    $r['result'] = 'Success';
133                } else {
134                    $r['result'] = 'Failure';
135                    $r['errors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
136                }
137            } else {
138                $r['result'] = 'Failure';
139                $r['errors'] = $this->getErrorFormatter()->arrayFromStatus(
140                    Status::newFatal( ApiMessage::create( $err->getMsg() ) )
141                );
142            }
143            $result[] = $r;
144        }
145        $apiResult = $this->getResult();
146        ApiResult::setIndexedTagName( $result, 'page' );
147        $apiResult->addValue( null, $this->getModuleName(), $result );
148
149        $this->setContinuationManager( null );
150        $continuationManager->setContinuationIntoResult( $apiResult );
151    }
152
153    /**
154     * Get a cached instance of an ApiPageSet object
155     * @return ApiPageSet
156     */
157    private function getPageSet() {
158        $this->mPageSet ??= new ApiPageSet( $this, 0, NS_FILE );
159
160        return $this->mPageSet;
161    }
162
163    /** @inheritDoc */
164    public function mustBePosted() {
165        return true;
166    }
167
168    /** @inheritDoc */
169    public function isWriteMode() {
170        return true;
171    }
172
173    /** @inheritDoc */
174    public function getAllowedParams( $flags = 0 ) {
175        $result = [
176            'rotation' => [
177                ParamValidator::PARAM_TYPE => [ '90', '180', '270' ],
178                ParamValidator::PARAM_REQUIRED => true
179            ],
180            'continue' => [
181                ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
182            ],
183            'tags' => [
184                ParamValidator::PARAM_TYPE => 'tags',
185                ParamValidator::PARAM_ISMULTI => true,
186            ],
187        ];
188        if ( $flags ) {
189            $result += $this->getPageSet()->getFinalParams( $flags );
190        }
191
192        return $result;
193    }
194
195    /** @inheritDoc */
196    public function needsToken() {
197        return 'csrf';
198    }
199
200    /** @inheritDoc */
201    protected function getExamplesMessages() {
202        return [
203            'action=imagerotate&titles=File:Example.jpg&rotation=90&token=123ABC'
204                => 'apihelp-imagerotate-example-simple',
205            'action=imagerotate&generator=categorymembers&gcmtitle=Category:Flip&gcmtype=file&' .
206                'rotation=180&token=123ABC'
207                => 'apihelp-imagerotate-example-generator',
208        ];
209    }
210
211    /** @inheritDoc */
212    public function getHelpUrls() {
213        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Imagerotate';
214    }
215}
216
217/** @deprecated class alias since 1.43 */
218class_alias( ApiImageRotate::class, 'ApiImageRotate' );