Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.51% covered (warning)
85.51%
59 / 69
45.45% covered (danger)
45.45%
5 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
MediaFileHandler
85.51% covered (warning)
85.51%
59 / 69
45.45% covered (danger)
45.45%
5 / 11
24.61
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPage
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getFile
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 run
78.95% covered (warning)
78.95%
15 / 19
0.00% covered (danger)
0.00%
0 / 1
5.23
 getResponse
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
1
 needsWriteAccess
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getParamSettings
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 getETag
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 getLastModified
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 hasRepresentation
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getResponseBodySchemaFileName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Rest\Handler;
4
5use MediaWiki\FileRepo\File\File;
6use MediaWiki\FileRepo\RepoGroup;
7use MediaWiki\Page\ExistingPageRecord;
8use MediaWiki\Page\PageLookup;
9use MediaWiki\Rest\Handler;
10use MediaWiki\Rest\LocalizedHttpException;
11use MediaWiki\Rest\Response;
12use MediaWiki\Rest\SimpleHandler;
13use Wikimedia\Message\MessageValue;
14use Wikimedia\ParamValidator\ParamValidator;
15
16/**
17 * Handler class for media meta-data
18 */
19class MediaFileHandler extends SimpleHandler {
20    use \MediaWiki\FileRepo\File\MediaFileTrait;
21
22    /**
23     * @var ExistingPageRecord|false|null
24     */
25    private $page = false;
26
27    /**
28     * @var File|false|null
29     */
30    private $file = false;
31
32    public function __construct(
33        private readonly RepoGroup $repoGroup,
34        private readonly PageLookup $pageLookup,
35    ) {
36    }
37
38    private function getPage(): ?ExistingPageRecord {
39        if ( $this->page === false ) {
40            $this->page = $this->pageLookup->getExistingPageByText(
41                    $this->getValidatedParams()['title'], NS_FILE
42                );
43        }
44        return $this->page;
45    }
46
47    private function getFile(): ?File {
48        if ( $this->file === false ) {
49            $page = $this->getPage();
50            $this->file =
51                // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
52                $this->repoGroup->findFile( $page, [ 'private' => $this->getAuthority() ] ) ?: null;
53        }
54        return $this->file;
55    }
56
57    /**
58     * @param string $title
59     * @return Response
60     * @throws LocalizedHttpException
61     */
62    public function run( $title ) {
63        $page = $this->getPage();
64
65        if ( !$page ) {
66            throw new LocalizedHttpException(
67                MessageValue::new( 'rest-nonexistent-title' )->plaintextParams( $title ),
68                404
69            );
70        }
71
72        if ( !$this->getAuthority()->authorizeRead( 'read', $page ) ) {
73            throw new LocalizedHttpException(
74                MessageValue::new( 'rest-permission-denied-title' )->plaintextParams( $title ),
75                403
76            );
77        }
78
79        $fileObj = $this->getFile();
80        if ( !$fileObj || !$fileObj->exists() ) {
81            throw new LocalizedHttpException(
82                MessageValue::new( 'rest-cannot-load-file' )->plaintextParams( $title ),
83                404
84            );
85        }
86
87        $response = $this->getResponse( $fileObj );
88        return $this->getResponseFactory()->createJson( $response );
89    }
90
91    /**
92     * @param File $file the file to load media links for
93     * @return array response data
94     */
95    private function getResponse( File $file ): array {
96        [ $maxWidth, $maxHeight ] = self::getImageLimitsFromOption(
97            $this->getAuthority()->getUser(), 'imagesize'
98        );
99        [ $maxThumbWidth, $maxThumbHeight ] = self::getImageLimitsFromOption(
100            $this->getAuthority()->getUser(), 'thumbsize'
101        );
102
103        // Normalize thumbnail sizes
104        [ $maxNormalizedWidth, $maxNormalizedHeight ] = self::getNormalizedThumbLimits( $maxThumbWidth );
105
106        $transforms = [
107            'preferred' => [
108                'maxWidth' => $maxWidth,
109                'maxHeight' => $maxHeight
110            ],
111            'thumbnail' => [
112                'maxWidth' => $maxNormalizedWidth,
113                'maxHeight' => $maxNormalizedHeight
114            ]
115        ];
116
117        return $this->getFileInfo( $file, $this->getAuthority(), $transforms );
118    }
119
120    /** @inheritDoc */
121    public function needsWriteAccess() {
122        return false;
123    }
124
125    /** @inheritDoc */
126    public function getParamSettings() {
127        return [
128            'title' => [
129                self::PARAM_SOURCE => 'path',
130                ParamValidator::PARAM_TYPE => 'string',
131                ParamValidator::PARAM_REQUIRED => true,
132                Handler::PARAM_DESCRIPTION => new MessageValue( 'rest-param-desc-media-file-title' ),
133                Handler::PARAM_EXAMPLE => 'File:Fennec_Fox.jpg',
134            ],
135        ];
136    }
137
138    /**
139     * @return string|null
140     * @throws LocalizedHttpException
141     */
142    protected function getETag(): ?string {
143        $file = $this->getFile();
144        if ( !$file || !$file->exists() ) {
145            return null;
146        }
147
148        return '"' . $file->getSha1() . '"';
149    }
150
151    /**
152     * @return string|null
153     * @throws LocalizedHttpException
154     */
155    protected function getLastModified(): ?string {
156        $file = $this->getFile();
157        if ( !$file || !$file->exists() ) {
158            return null;
159        }
160
161        return $file->getTimestamp();
162    }
163
164    /**
165     * @return bool
166     */
167    protected function hasRepresentation() {
168        $file = $this->getFile();
169        return $file && $file->exists();
170    }
171
172    public function getResponseBodySchemaFileName( string $method ): ?string {
173        return __DIR__ . '/Schema/MediaFile.json';
174    }
175}