Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
61.72% covered (warning)
61.72%
79 / 128
50.00% covered (danger)
50.00%
4 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiQueryPageImages
61.72% covered (warning)
61.72%
79 / 128
50.00% covered (danger)
50.00%
4 / 8
84.91
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getTitles
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 execute
81.82% covered (warning)
81.82%
36 / 44
0.00% covered (danger)
0.00%
0 / 1
14.02
 getCacheMode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setResultValues
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
132
 getAllowedParams
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
1 / 1
1
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 4
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
3namespace PageImages;
4
5use MediaWiki\Api\ApiBase;
6use MediaWiki\Api\ApiQuery;
7use MediaWiki\Api\ApiQueryBase;
8use MediaWiki\Page\PageReference;
9use MediaWiki\Page\PageReferenceValue;
10use RepoGroup;
11use Wikimedia\ParamValidator\ParamValidator;
12use 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 */
25class 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}