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