Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
93.51% |
72 / 77 |
|
81.82% |
9 / 11 |
CRAP | |
0.00% |
0 / 1 |
MediaLinksHandler | |
93.51% |
72 / 77 |
|
81.82% |
9 / 11 |
18.09 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getPage | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
run | |
80.95% |
17 / 21 |
|
0.00% |
0 / 1 |
4.11 | |||
getDbResults | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
processDbResults | |
100.00% |
25 / 25 |
|
100.00% |
1 / 1 |
2 | |||
needsWriteAccess | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getParamSettings | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
getETag | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getLastModified | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
hasRepresentation | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMaxNumLinks | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Rest\Handler; |
4 | |
5 | use MediaFileTrait; |
6 | use MediaWiki\Page\ExistingPageRecord; |
7 | use MediaWiki\Page\PageLookup; |
8 | use MediaWiki\Rest\LocalizedHttpException; |
9 | use MediaWiki\Rest\Response; |
10 | use MediaWiki\Rest\SimpleHandler; |
11 | use RepoGroup; |
12 | use Wikimedia\Message\MessageValue; |
13 | use Wikimedia\ParamValidator\ParamValidator; |
14 | use Wikimedia\Rdbms\IConnectionProvider; |
15 | |
16 | /** |
17 | * Handler class for Core REST API endpoints that perform operations on revisions |
18 | */ |
19 | class MediaLinksHandler extends SimpleHandler { |
20 | use MediaFileTrait; |
21 | |
22 | /** int The maximum number of media links to return */ |
23 | private const MAX_NUM_LINKS = 100; |
24 | |
25 | /** @var IConnectionProvider */ |
26 | private $dbProvider; |
27 | |
28 | /** @var RepoGroup */ |
29 | private $repoGroup; |
30 | |
31 | /** @var PageLookup */ |
32 | private $pageLookup; |
33 | |
34 | /** |
35 | * @var ExistingPageRecord|false|null |
36 | */ |
37 | private $page = false; |
38 | |
39 | /** |
40 | * @param IConnectionProvider $dbProvider |
41 | * @param RepoGroup $repoGroup |
42 | * @param PageLookup $pageLookup |
43 | */ |
44 | public function __construct( |
45 | IConnectionProvider $dbProvider, |
46 | RepoGroup $repoGroup, |
47 | PageLookup $pageLookup |
48 | ) { |
49 | $this->dbProvider = $dbProvider; |
50 | $this->repoGroup = $repoGroup; |
51 | $this->pageLookup = $pageLookup; |
52 | } |
53 | |
54 | /** |
55 | * @return ExistingPageRecord|null |
56 | */ |
57 | private function getPage(): ?ExistingPageRecord { |
58 | if ( $this->page === false ) { |
59 | $this->page = $this->pageLookup->getExistingPageByText( |
60 | $this->getValidatedParams()['title'] |
61 | ); |
62 | } |
63 | return $this->page; |
64 | } |
65 | |
66 | /** |
67 | * @param string $title |
68 | * @return Response |
69 | * @throws LocalizedHttpException |
70 | */ |
71 | public function run( $title ) { |
72 | $page = $this->getPage(); |
73 | if ( !$page ) { |
74 | throw new LocalizedHttpException( |
75 | MessageValue::new( 'rest-nonexistent-title' )->plaintextParams( $title ), |
76 | 404 |
77 | ); |
78 | } |
79 | |
80 | if ( !$this->getAuthority()->authorizeRead( 'read', $page ) ) { |
81 | throw new LocalizedHttpException( |
82 | MessageValue::new( 'rest-permission-denied-title' )->plaintextParams( $title ), |
83 | 403 |
84 | ); |
85 | } |
86 | |
87 | // @todo: add continuation if too many links are found |
88 | $results = $this->getDbResults( $page->getId() ); |
89 | if ( count( $results ) > $this->getMaxNumLinks() ) { |
90 | throw new LocalizedHttpException( |
91 | MessageValue::new( 'rest-media-too-many-links' ) |
92 | ->plaintextParams( $title ) |
93 | ->numParams( $this->getMaxNumLinks() ), |
94 | 400 |
95 | ); |
96 | } |
97 | $response = $this->processDbResults( $results ); |
98 | return $this->getResponseFactory()->createJson( $response ); |
99 | } |
100 | |
101 | /** |
102 | * @param int $pageId the id of the page to load media links for |
103 | * @return array the results |
104 | */ |
105 | private function getDbResults( int $pageId ) { |
106 | return $this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder() |
107 | ->select( 'il_to' ) |
108 | ->from( 'imagelinks' ) |
109 | ->where( [ 'il_from' => $pageId ] ) |
110 | ->orderBy( 'il_to' ) |
111 | ->limit( $this->getMaxNumLinks() + 1 ) |
112 | ->caller( __METHOD__ )->fetchFieldValues(); |
113 | } |
114 | |
115 | /** |
116 | * @param array $results database results, or an empty array if none |
117 | * @return array response data |
118 | */ |
119 | private function processDbResults( $results ) { |
120 | // Using "private" here means an equivalent of the Action API's "anon-public-user-private" |
121 | // caching model would be necessary, if caching is ever added to this endpoint. |
122 | $performer = $this->getAuthority(); |
123 | $findTitles = array_map( static function ( $title ) use ( $performer ) { |
124 | return [ |
125 | 'title' => $title, |
126 | 'private' => $performer, |
127 | ]; |
128 | }, $results ); |
129 | |
130 | $files = $this->repoGroup->findFiles( $findTitles ); |
131 | [ $maxWidth, $maxHeight ] = self::getImageLimitsFromOption( |
132 | $this->getAuthority()->getUser(), |
133 | 'imagesize' |
134 | ); |
135 | $transforms = [ |
136 | 'preferred' => [ |
137 | 'maxWidth' => $maxWidth, |
138 | 'maxHeight' => $maxHeight, |
139 | ] |
140 | ]; |
141 | $response = []; |
142 | foreach ( $files as $file ) { |
143 | $response[] = $this->getFileInfo( $file, $performer, $transforms ); |
144 | } |
145 | |
146 | $response = [ |
147 | 'files' => $response |
148 | ]; |
149 | |
150 | return $response; |
151 | } |
152 | |
153 | public function needsWriteAccess() { |
154 | return false; |
155 | } |
156 | |
157 | public function getParamSettings() { |
158 | return [ |
159 | 'title' => [ |
160 | self::PARAM_SOURCE => 'path', |
161 | ParamValidator::PARAM_TYPE => 'string', |
162 | ParamValidator::PARAM_REQUIRED => true, |
163 | ], |
164 | ]; |
165 | } |
166 | |
167 | /** |
168 | * @return string|null |
169 | * @throws LocalizedHttpException |
170 | */ |
171 | protected function getETag(): ?string { |
172 | $page = $this->getPage(); |
173 | if ( !$page ) { |
174 | return null; |
175 | } |
176 | |
177 | // XXX: use hash of the rendered HTML? |
178 | return '"' . $page->getLatest() . '@' . wfTimestamp( TS_MW, $page->getTouched() ) . '"'; |
179 | } |
180 | |
181 | /** |
182 | * @return string|null |
183 | * @throws LocalizedHttpException |
184 | */ |
185 | protected function getLastModified(): ?string { |
186 | $page = $this->getPage(); |
187 | return $page ? $page->getTouched() : null; |
188 | } |
189 | |
190 | /** |
191 | * @return bool |
192 | */ |
193 | protected function hasRepresentation() { |
194 | return (bool)$this->getPage(); |
195 | } |
196 | |
197 | /** |
198 | * For testing |
199 | * |
200 | * @unstable |
201 | */ |
202 | protected function getMaxNumLinks(): int { |
203 | return self::MAX_NUM_LINKS; |
204 | } |
205 | } |