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 * @return null 67 */ 68 public function execute() { 69 $params = $this->extractRequestParams(); 70 $prop = array_flip( $params['prop'] ); 71 if ( !count( $prop ) ) { 72 $this->dieWithError( 73 [ 'apierror-paramempty', $this->encodeParamName( 'prop' ) ], 'noprop' 74 ); 75 } 76 77 $allTitles = $this->getTitles(); 78 79 if ( count( $allTitles ) === 0 ) { 80 return; 81 } 82 83 // Find the offset based on the continue param 84 $offset = 0; 85 if ( isset( $params['continue'] ) ) { 86 // Get the position (not the key) of the 'continue' page within the 87 // array of titles. Set this as the offset. 88 $pageIds = array_keys( $allTitles ); 89 $offset = array_search( intval( $params['continue'] ), $pageIds ); 90 // If the 'continue' page wasn't found, die with error 91 $this->dieContinueUsageIf( !$offset ); 92 } 93 94 $limit = $params['limit']; 95 // Slice the part of the array we want to find images for 96 $titles = array_slice( $allTitles, $offset, $limit, true ); 97 98 // Get the next item in the title array and use it to set the continue value 99 $nextItemArray = array_slice( $allTitles, $offset + $limit, 1, true ); 100 if ( $nextItemArray ) { 101 $this->setContinueEnumParameter( 'continue', key( $nextItemArray ) ); 102 } 103 104 // Find any titles in the file namespace so we can handle those separately 105 $filePageTitles = []; 106 foreach ( $titles as $id => $title ) { 107 if ( $title->inNamespace( NS_FILE ) ) { 108 $filePageTitles[$id] = $title; 109 unset( $titles[$id] ); 110 } 111 } 112 113 $size = $params['thumbsize']; 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 ); 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 ); 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 */ 165 protected function setResultValues( array $prop, $pageId, $fileName, $size ) { 166 $vals = []; 167 if ( isset( $prop['thumbnail'] ) || isset( $prop['original'] ) ) { 168 $file = MediaWikiServices::getInstance()->getRepoGroup()->findFile( $fileName ); 169 if ( $file ) { 170 if ( isset( $prop['thumbnail'] ) ) { 171 $thumb = $file->transform( [ 'width' => $size, 'height' => $size ] ); 172 if ( $thumb && $thumb->getUrl() ) { 173 // You can request a thumb 1000x larger than the original 174 // which (in case of bitmap original) will return a Thumb object 175 // that will lie about its size but have the original as an image. 176 $reportedSize = $thumb->fileIsSource() ? $file : $thumb; 177 $vals['thumbnail'] = [ 178 'source' => wfExpandUrl( $thumb->getUrl(), PROTO_CURRENT ), 179 'width' => $reportedSize->getWidth(), 180 'height' => $reportedSize->getHeight(), 181 ]; 182 } 183 } 184 185 if ( isset( $prop['original'] ) ) { 186 $original_url = wfExpandUrl( $file->getUrl(), PROTO_CURRENT ); 187 188 $vals['original'] = [ 189 'source' => $original_url, 190 'width' => $file->getWidth(), 191 'height' => $file->getHeight() 192 ]; 193 } 194 } 195 } 196 197 if ( isset( $prop['name'] ) ) { 198 $vals['pageimage'] = $fileName; 199 } 200 201 $this->getResult()->addValue( [ 'query', 'pages' ], $pageId, $vals ); 202 } 203 204 /** 205 * Return an array describing all possible parameters to this module 206 * @return array 207 */ 208 public function getAllowedParams() { 209 return [ 210 'prop' => [ 211 ApiBase::PARAM_TYPE => [ 'thumbnail', 'name', 'original' ], 212 ApiBase::PARAM_ISMULTI => true, 213 ApiBase::PARAM_DFLT => 'thumbnail|name', 214 ], 215 'thumbsize' => [ 216 ApiBase::PARAM_TYPE => 'integer', 217 ApiBase::PARAM_DFLT => 50, 218 ], 219 'limit' => [ 220 ApiBase::PARAM_DFLT => 50, 221 ApiBase::PARAM_TYPE => 'limit', 222 ApiBase::PARAM_MIN => 1, 223 ApiBase::PARAM_MAX => 50, 224 ApiBase::PARAM_MAX2 => 100, 225 ], 226 'license' => [ 227 ApiBase::PARAM_TYPE => [ PageImages::LICENSE_FREE, PageImages::LICENSE_ANY ], 228 ApiBase::PARAM_ISMULTI => false, 229 ApiBase::PARAM_DFLT => $this->getConfig()->get( 'PageImagesAPIDefaultLicense' ), 230 ], 231 'continue' => [ 232 ApiBase::PARAM_TYPE => 'integer', 233 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue', 234 ], 235 ]; 236 } 237 238 /** 239 * @inheritDoc 240 */ 241 protected function getExamplesMessages() { 242 return [ 243 'action=query&prop=pageimages&titles=Albert%20Einstein&pithumbsize=100' => 244 'apihelp-query+pageimages-example-1', 245 ]; 246 } 247 248 /** 249 * @see ApiBase::getHelpUrls() 250 * @return string 251 */ 252 public function getHelpUrls() { 253 return "https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:PageImages#API"; 254 } 255 256} 257