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