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