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