Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
61.72% |
79 / 128 |
|
50.00% |
4 / 8 |
CRAP | |
0.00% |
0 / 1 |
ApiQueryPageImages | |
61.72% |
79 / 128 |
|
50.00% |
4 / 8 |
84.91 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getTitles | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
execute | |
81.82% |
36 / 44 |
|
0.00% |
0 / 1 |
14.02 | |||
getCacheMode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setResultValues | |
0.00% |
0 / 36 |
|
0.00% |
0 / 1 |
132 | |||
getAllowedParams | |
100.00% |
33 / 33 |
|
100.00% |
1 / 1 |
1 | |||
getExamplesMessages | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getHelpUrls | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace PageImages; |
4 | |
5 | use MediaWiki\Api\ApiBase; |
6 | use MediaWiki\Api\ApiQuery; |
7 | use MediaWiki\Api\ApiQueryBase; |
8 | use MediaWiki\Page\PageReference; |
9 | use MediaWiki\Page\PageReferenceValue; |
10 | use RepoGroup; |
11 | use Wikimedia\ParamValidator\ParamValidator; |
12 | use Wikimedia\ParamValidator\TypeDef\IntegerDef; |
13 | |
14 | /** |
15 | * Expose image information for a page via a new prop=pageimages API. |
16 | * |
17 | * @see https://www.mediawiki.org/wiki/Extension:PageImages#API |
18 | * |
19 | * @license WTFPL |
20 | * @author Max Semenik |
21 | * @author Ryan Kaldari |
22 | * @author Yuvi Panda |
23 | * @author Sam Smith |
24 | */ |
25 | class ApiQueryPageImages extends ApiQueryBase { |
26 | private RepoGroup $repoGroup; |
27 | |
28 | public function __construct( |
29 | ApiQuery $query, |
30 | string $moduleName, |
31 | RepoGroup $repoGroup |
32 | ) { |
33 | parent::__construct( $query, $moduleName, 'pi' ); |
34 | $this->repoGroup = $repoGroup; |
35 | } |
36 | |
37 | /** |
38 | * Gets the set of titles to get page images for. |
39 | * |
40 | * Note well that the set of titles comprises the set of "good" titles |
41 | * (see {@see ApiPageSet::getGoodPages}) union the set of "missing" |
42 | * titles in the File namespace that might correspond to foreign files. |
43 | * The latter are included because titles in the File namespace are |
44 | * expected to be found with {@see \RepoGroup::findFile}. |
45 | * |
46 | * @return PageReference[] A map of page ID, which will be negative in the case |
47 | * of missing titles in the File namespace, to PageReference object |
48 | */ |
49 | protected function getTitles() { |
50 | $pageSet = $this->getPageSet(); |
51 | $titles = $pageSet->getGoodPages(); |
52 | |
53 | // T98791: We want foreign files to be treated like local files |
54 | // in #execute, so include the set of missing filespace pages, |
55 | // which were initially rejected in ApiPageSet#execute. |
56 | $missingTitles = $pageSet->getMissingTitlesByNamespace(); |
57 | $missingFileTitles = $missingTitles[NS_FILE] ?? []; |
58 | |
59 | // $titles is a map of ID to title object, which is ideal, |
60 | // whereas $missingFileTitles is a map of title text to ID. |
61 | // Do not use array_merge here as it doesn't preserve keys. |
62 | foreach ( $missingFileTitles as $dbkey => $id ) { |
63 | $titles[$id] = PageReferenceValue::localReference( NS_FILE, $dbkey ); |
64 | } |
65 | |
66 | return $titles; |
67 | } |
68 | |
69 | /** |
70 | * Evaluates the parameters, performs the requested retrieval of page images, |
71 | * and sets up the result |
72 | */ |
73 | public function execute() { |
74 | $params = $this->extractRequestParams(); |
75 | $prop = array_flip( $params['prop'] ); |
76 | if ( !count( $prop ) ) { |
77 | $this->dieWithError( |
78 | [ 'apierror-paramempty', $this->encodeParamName( 'prop' ) ], 'noprop' |
79 | ); |
80 | } |
81 | |
82 | $allTitles = $this->getTitles(); |
83 | |
84 | if ( count( $allTitles ) === 0 ) { |
85 | return; |
86 | } |
87 | |
88 | // Find the offset based on the continue param |
89 | $offset = 0; |
90 | if ( isset( $params['continue'] ) ) { |
91 | // Get the position (not the key) of the 'continue' page within the |
92 | // array of titles. Set this as the offset. |
93 | $pageIds = array_keys( $allTitles ); |
94 | $offset = array_search( intval( $params['continue'] ), $pageIds ); |
95 | // If the 'continue' page wasn't found, die with error |
96 | $this->dieContinueUsageIf( !$offset ); |
97 | } |
98 | |
99 | $limit = $params['limit']; |
100 | // Slice the part of the array we want to find images for |
101 | $titles = array_slice( $allTitles, $offset, $limit, true ); |
102 | |
103 | // Get the next item in the title array and use it to set the continue value |
104 | $nextItemArray = array_slice( $allTitles, $offset + $limit, 1, true ); |
105 | if ( $nextItemArray ) { |
106 | $this->setContinueEnumParameter( 'continue', key( $nextItemArray ) ); |
107 | } |
108 | |
109 | // Find any titles in the file namespace so we can handle those separately |
110 | $filePageTitles = []; |
111 | foreach ( $titles as $id => $title ) { |
112 | if ( $title->getNamespace() === NS_FILE ) { |
113 | $filePageTitles[$id] = $title; |
114 | unset( $titles[$id] ); |
115 | } |
116 | } |
117 | |
118 | $size = $params['thumbsize']; |
119 | $lang = $params['langcode']; |
120 | // Extract page images from the page_props table |
121 | if ( count( $titles ) > 0 ) { |
122 | $this->addTables( 'page_props' ); |
123 | $this->addFields( [ 'pp_page', 'pp_propname', 'pp_value' ] ); |
124 | $this->addWhere( [ 'pp_page' => array_keys( $titles ), |
125 | 'pp_propname' => PageImages::getPropNames( $params['license'] ) ] ); |
126 | |
127 | $res = $this->select( __METHOD__ ); |
128 | |
129 | $buffer = []; |
130 | $propNameAny = PageImages::getPropName( false ); |
131 | foreach ( $res as $row ) { |
132 | $pageId = $row->pp_page; |
133 | if ( !array_key_exists( $pageId, $buffer ) || $row->pp_propname === $propNameAny ) { |
134 | $buffer[$pageId] = $row; |
135 | } |
136 | } |
137 | |
138 | foreach ( $buffer as $pageId => $row ) { |
139 | $fileName = $row->pp_value; |
140 | $this->setResultValues( $prop, $pageId, $fileName, $size, $lang ); |
141 | } |
142 | // End page props image extraction |
143 | } |
144 | |
145 | // Extract images from file namespace pages. In this case we just use |
146 | // the file itself rather than searching for a page_image. (Bug 50252) |
147 | foreach ( $filePageTitles as $pageId => $title ) { |
148 | $fileName = $title->getDBkey(); |
149 | $this->setResultValues( $prop, $pageId, $fileName, $size, $lang ); |
150 | } |
151 | } |
152 | |
153 | /** |
154 | * Get the cache mode for the data generated by this module |
155 | * |
156 | * @param array $params Ignored parameters |
157 | * @return string Always returns "public" |
158 | */ |
159 | public function getCacheMode( $params ) { |
160 | return 'public'; |
161 | } |
162 | |
163 | /** |
164 | * For a given page, set API return values for thumbnail and pageimage as needed |
165 | * |
166 | * @param array $prop The prop values from the API request |
167 | * @param int $pageId The ID of the page |
168 | * @param string $fileName The name of the file to transform |
169 | * @param int $size The thumbsize value from the API request |
170 | * @param string $lang The language code from the API request |
171 | */ |
172 | protected function setResultValues( array $prop, $pageId, $fileName, $size, $lang ) { |
173 | $vals = []; |
174 | if ( isset( $prop['thumbnail'] ) || isset( $prop['original'] ) ) { |
175 | $file = $this->repoGroup->findFile( $fileName ); |
176 | if ( $file ) { |
177 | if ( isset( $prop['thumbnail'] ) ) { |
178 | $thumb = $file->transform( [ |
179 | 'width' => $size, |
180 | 'lang' => $lang |
181 | ] ); |
182 | if ( $thumb && $thumb->getUrl() ) { |
183 | // You can request a thumb 1000x larger than the original |
184 | // which (in case of bitmap original) will return a Thumb object |
185 | // that will lie about its size but have the original as an image. |
186 | $reportedSize = $thumb->fileIsSource() ? $file : $thumb; |
187 | $vals['thumbnail'] = [ |
188 | 'source' => wfExpandUrl( $thumb->getUrl(), PROTO_CURRENT ), |
189 | 'width' => $reportedSize->getWidth(), |
190 | 'height' => $reportedSize->getHeight(), |
191 | ]; |
192 | } |
193 | } |
194 | |
195 | if ( isset( $prop['original'] ) ) { |
196 | $originalSize = [ |
197 | 'width' => $file->getWidth(), |
198 | 'height' => $file->getHeight() |
199 | ]; |
200 | if ( $lang ) { |
201 | $file = $file->transform( [ |
202 | 'lang' => $lang, |
203 | 'width' => $originalSize['width'], |
204 | 'height' => $originalSize['height'] |
205 | ] ); |
206 | } |
207 | $original_url = wfExpandUrl( $file->getUrl(), PROTO_CURRENT ); |
208 | |
209 | $vals['original'] = [ |
210 | 'source' => $original_url, |
211 | 'width' => $originalSize['width'], |
212 | 'height' => $originalSize['height'] |
213 | ]; |
214 | } |
215 | } |
216 | } |
217 | |
218 | if ( isset( $prop['name'] ) ) { |
219 | $vals['pageimage'] = $fileName; |
220 | } |
221 | |
222 | $this->getResult()->addValue( [ 'query', 'pages' ], $pageId, $vals ); |
223 | } |
224 | |
225 | /** |
226 | * Return an array describing all possible parameters to this module |
227 | * @return array |
228 | */ |
229 | public function getAllowedParams() { |
230 | return [ |
231 | 'prop' => [ |
232 | ParamValidator::PARAM_TYPE => [ 'thumbnail', 'name', 'original' ], |
233 | ParamValidator::PARAM_ISMULTI => true, |
234 | ParamValidator::PARAM_DEFAULT => 'thumbnail|name', |
235 | ApiBase::PARAM_HELP_MSG_PER_VALUE => [], |
236 | ], |
237 | 'thumbsize' => [ |
238 | ParamValidator::PARAM_TYPE => 'integer', |
239 | ParamValidator::PARAM_DEFAULT => 50, |
240 | ], |
241 | 'limit' => [ |
242 | ParamValidator::PARAM_DEFAULT => 50, |
243 | ParamValidator::PARAM_TYPE => 'limit', |
244 | IntegerDef::PARAM_MIN => 1, |
245 | IntegerDef::PARAM_MAX => 50, |
246 | IntegerDef::PARAM_MAX2 => 100, |
247 | ], |
248 | 'license' => [ |
249 | ParamValidator::PARAM_TYPE => [ PageImages::LICENSE_FREE, PageImages::LICENSE_ANY ], |
250 | ParamValidator::PARAM_ISMULTI => false, |
251 | ParamValidator::PARAM_DEFAULT => $this->getConfig()->get( 'PageImagesAPIDefaultLicense' ), |
252 | ApiBase::PARAM_HELP_MSG_PER_VALUE => [], |
253 | ], |
254 | 'continue' => [ |
255 | ParamValidator::PARAM_TYPE => 'integer', |
256 | ApiBase::PARAM_HELP_MSG => 'api-help-param-continue', |
257 | ], |
258 | 'langcode' => [ |
259 | ParamValidator::PARAM_TYPE => 'string', |
260 | ParamValidator::PARAM_DEFAULT => null |
261 | ] |
262 | ]; |
263 | } |
264 | |
265 | /** |
266 | * @inheritDoc |
267 | */ |
268 | protected function getExamplesMessages() { |
269 | return [ |
270 | 'action=query&prop=pageimages&titles=Albert%20Einstein&pithumbsize=100' => |
271 | 'apihelp-query+pageimages-example-1', |
272 | ]; |
273 | } |
274 | |
275 | /** |
276 | * @see ApiBase::getHelpUrls() |
277 | * @return string |
278 | */ |
279 | public function getHelpUrls() { |
280 | return "https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:PageImages#API"; |
281 | } |
282 | |
283 | } |