MediaWiki
REL1_19
|
00001 <?php 00024 class ForeignAPIRepo extends FileRepo { 00025 /* This version string is used in the user agent for requests and will help 00026 * server maintainers in identify ForeignAPI usage. 00027 * Update the version every time you make breaking or significant changes. */ 00028 const VERSION = "2.1"; 00029 00030 var $fileFactory = array( 'ForeignAPIFile', 'newFromTitle' ); 00031 /* Check back with Commons after a day */ 00032 var $apiThumbCacheExpiry = 86400; /* 24*60*60 */ 00033 /* Redownload thumbnail files after a month */ 00034 var $fileCacheExpiry = 2592000; /* 86400*30 */ 00035 00036 protected $mQueryCache = array(); 00037 protected $mFileExists = array(); 00038 00039 function __construct( $info ) { 00040 global $wgLocalFileRepo; 00041 parent::__construct( $info ); 00042 00043 // http://commons.wikimedia.org/w/api.php 00044 $this->mApiBase = isset( $info['apibase'] ) ? $info['apibase'] : null; 00045 00046 if( isset( $info['apiThumbCacheExpiry'] ) ) { 00047 $this->apiThumbCacheExpiry = $info['apiThumbCacheExpiry']; 00048 } 00049 if( isset( $info['fileCacheExpiry'] ) ) { 00050 $this->fileCacheExpiry = $info['fileCacheExpiry']; 00051 } 00052 if( !$this->scriptDirUrl ) { 00053 // hack for description fetches 00054 $this->scriptDirUrl = dirname( $this->mApiBase ); 00055 } 00056 // If we can cache thumbs we can guess sane defaults for these 00057 if( $this->canCacheThumbs() && !$this->url ) { 00058 $this->url = $wgLocalFileRepo['url']; 00059 } 00060 if( $this->canCacheThumbs() && !$this->thumbUrl ) { 00061 $this->thumbUrl = $this->url . '/thumb'; 00062 } 00063 } 00064 00071 function newFile( $title, $time = false ) { 00072 if ( $time ) { 00073 return false; 00074 } 00075 return parent::newFile( $title, $time ); 00076 } 00077 00082 function storeBatch( $triplets, $flags = 0 ) { 00083 return false; 00084 } 00085 00086 function storeTemp( $originalName, $srcPath ) { 00087 return false; 00088 } 00089 00090 function concatenate( $fileList, $targetPath, $flags = 0 ){ 00091 return false; 00092 } 00093 00094 function append( $srcPath, $toAppendPath, $flags = 0 ){ 00095 return false; 00096 } 00097 00098 function appendFinish( $toAppendPath ){ 00099 return false; 00100 } 00101 00102 function publishBatch( $triplets, $flags = 0 ) { 00103 return false; 00104 } 00105 00106 function deleteBatch( $sourceDestPairs ) { 00107 return false; 00108 } 00109 00110 function fileExistsBatch( $files, $flags = 0 ) { 00111 $results = array(); 00112 foreach ( $files as $k => $f ) { 00113 if ( isset( $this->mFileExists[$k] ) ) { 00114 $results[$k] = true; 00115 unset( $files[$k] ); 00116 } elseif( self::isVirtualUrl( $f ) ) { 00117 # @todo FIXME: We need to be able to handle virtual 00118 # URLs better, at least when we know they refer to the 00119 # same repo. 00120 $results[$k] = false; 00121 unset( $files[$k] ); 00122 } 00123 } 00124 00125 $data = $this->fetchImageQuery( array( 'titles' => implode( $files, '|' ), 00126 'prop' => 'imageinfo' ) ); 00127 if( isset( $data['query']['pages'] ) ) { 00128 $i = 0; 00129 foreach( $files as $key => $file ) { 00130 $results[$key] = $this->mFileExists[$key] = !isset( $data['query']['pages'][$i]['missing'] ); 00131 $i++; 00132 } 00133 } 00134 return $results; 00135 } 00136 00137 function getFileProps( $virtualUrl ) { 00138 return false; 00139 } 00140 00141 function fetchImageQuery( $query ) { 00142 global $wgMemc; 00143 00144 $query = array_merge( $query, 00145 array( 00146 'format' => 'json', 00147 'action' => 'query', 00148 'redirects' => 'true' 00149 ) ); 00150 if ( $this->mApiBase ) { 00151 $url = wfAppendQuery( $this->mApiBase, $query ); 00152 } else { 00153 $url = $this->makeUrl( $query, 'api' ); 00154 } 00155 00156 if( !isset( $this->mQueryCache[$url] ) ) { 00157 $key = $this->getLocalCacheKey( 'ForeignAPIRepo', 'Metadata', md5( $url ) ); 00158 $data = $wgMemc->get( $key ); 00159 if( !$data ) { 00160 $data = self::httpGet( $url ); 00161 if ( !$data ) { 00162 return null; 00163 } 00164 $wgMemc->set( $key, $data, 3600 ); 00165 } 00166 00167 if( count( $this->mQueryCache ) > 100 ) { 00168 // Keep the cache from growing infinitely 00169 $this->mQueryCache = array(); 00170 } 00171 $this->mQueryCache[$url] = $data; 00172 } 00173 return FormatJson::decode( $this->mQueryCache[$url], true ); 00174 } 00175 00176 function getImageInfo( $data ) { 00177 if( $data && isset( $data['query']['pages'] ) ) { 00178 foreach( $data['query']['pages'] as $info ) { 00179 if( isset( $info['imageinfo'][0] ) ) { 00180 return $info['imageinfo'][0]; 00181 } 00182 } 00183 } 00184 return false; 00185 } 00186 00187 function findBySha1( $hash ) { 00188 $results = $this->fetchImageQuery( array( 00189 'aisha1base36' => $hash, 00190 'aiprop' => ForeignAPIFile::getProps(), 00191 'list' => 'allimages', ) ); 00192 $ret = array(); 00193 if ( isset( $results['query']['allimages'] ) ) { 00194 foreach ( $results['query']['allimages'] as $img ) { 00195 // 1.14 was broken, doesn't return name attribute 00196 if( !isset( $img['name'] ) ) { 00197 continue; 00198 } 00199 $ret[] = new ForeignAPIFile( Title::makeTitle( NS_FILE, $img['name'] ), $this, $img ); 00200 } 00201 } 00202 return $ret; 00203 } 00204 00205 function getThumbUrl( $name, $width = -1, $height = -1, &$result = null, $otherParams = '' ) { 00206 $data = $this->fetchImageQuery( array( 00207 'titles' => 'File:' . $name, 00208 'iiprop' => 'url|timestamp', 00209 'iiurlwidth' => $width, 00210 'iiurlheight' => $height, 00211 'iiurlparam' => $otherParams, 00212 'prop' => 'imageinfo' ) ); 00213 $info = $this->getImageInfo( $data ); 00214 00215 if( $data && $info && isset( $info['thumburl'] ) ) { 00216 wfDebug( __METHOD__ . " got remote thumb " . $info['thumburl'] . "\n" ); 00217 $result = $info; 00218 return $info['thumburl']; 00219 } else { 00220 return false; 00221 } 00222 } 00223 00235 function getThumbUrlFromCache( $name, $width, $height, $params="" ) { 00236 global $wgMemc; 00237 00238 if ( !$this->canCacheThumbs() ) { 00239 $result = null; // can't pass "null" by reference, but it's ok as default value 00240 return $this->getThumbUrl( $name, $width, $height, $result, $params ); 00241 } 00242 $key = $this->getLocalCacheKey( 'ForeignAPIRepo', 'ThumbUrl', $name ); 00243 $sizekey = "$width:$height:$params"; 00244 00245 /* Get the array of urls that we already know */ 00246 $knownThumbUrls = $wgMemc->get($key); 00247 if( !$knownThumbUrls ) { 00248 /* No knownThumbUrls for this file */ 00249 $knownThumbUrls = array(); 00250 } else { 00251 if( isset( $knownThumbUrls[$sizekey] ) ) { 00252 wfDebug( __METHOD__ . ': Got thumburl from local cache: ' . 00253 "{$knownThumbUrls[$sizekey]} \n"); 00254 return $knownThumbUrls[$sizekey]; 00255 } 00256 /* This size is not yet known */ 00257 } 00258 00259 $metadata = null; 00260 $foreignUrl = $this->getThumbUrl( $name, $width, $height, $metadata, $params ); 00261 00262 if( !$foreignUrl ) { 00263 wfDebug( __METHOD__ . " Could not find thumburl\n" ); 00264 return false; 00265 } 00266 00267 // We need the same filename as the remote one :) 00268 $fileName = rawurldecode( pathinfo( $foreignUrl, PATHINFO_BASENAME ) ); 00269 if( !$this->validateFilename( $fileName ) ) { 00270 wfDebug( __METHOD__ . " The deduced filename $fileName is not safe\n" ); 00271 return false; 00272 } 00273 $localPath = $this->getZonePath( 'thumb' ) . "/" . $this->getHashPath( $name ) . $name; 00274 $localFilename = $localPath . "/" . $fileName; 00275 $localUrl = $this->getZoneUrl( 'thumb' ) . "/" . $this->getHashPath( $name ) . rawurlencode( $name ) . "/" . rawurlencode( $fileName ); 00276 00277 if( $this->fileExists( $localFilename ) && isset( $metadata['timestamp'] ) ) { 00278 wfDebug( __METHOD__ . " Thumbnail was already downloaded before\n" ); 00279 $modified = $this->getFileTimestamp( $localFilename ); 00280 $remoteModified = strtotime( $metadata['timestamp'] ); 00281 $current = time(); 00282 $diff = abs( $modified - $current ); 00283 if( $remoteModified < $modified && $diff < $this->fileCacheExpiry ) { 00284 /* Use our current and already downloaded thumbnail */ 00285 $knownThumbUrls[$sizekey] = $localUrl; 00286 $wgMemc->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry ); 00287 return $localUrl; 00288 } 00289 /* There is a new Commons file, or existing thumbnail older than a month */ 00290 } 00291 $thumb = self::httpGet( $foreignUrl ); 00292 if( !$thumb ) { 00293 wfDebug( __METHOD__ . " Could not download thumb\n" ); 00294 return false; 00295 } 00296 00297 # @todo FIXME: Delete old thumbs that aren't being used. Maintenance script? 00298 wfSuppressWarnings(); 00299 $backend = $this->getBackend(); 00300 $op = array( 'op' => 'create', 'dst' => $localFilename, 'content' => $thumb ); 00301 if( !$backend->doOperation( $op )->isOK() ) { 00302 wfRestoreWarnings(); 00303 wfDebug( __METHOD__ . " could not write to thumb path\n" ); 00304 return $foreignUrl; 00305 } 00306 wfRestoreWarnings(); 00307 $knownThumbUrls[$sizekey] = $localUrl; 00308 $wgMemc->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry ); 00309 wfDebug( __METHOD__ . " got local thumb $localUrl, saving to cache \n" ); 00310 return $localUrl; 00311 } 00312 00316 function getZoneUrl( $zone ) { 00317 switch ( $zone ) { 00318 case 'public': 00319 return $this->url; 00320 case 'thumb': 00321 return $this->thumbUrl; 00322 default: 00323 return parent::getZoneUrl( $zone ); 00324 } 00325 } 00326 00330 function getZonePath( $zone ) { 00331 $supported = array( 'public', 'thumb' ); 00332 if ( in_array( $zone, $supported ) ) { 00333 return parent::getZonePath( $zone ); 00334 } 00335 return false; 00336 } 00337 00342 public function canCacheThumbs() { 00343 return ( $this->apiThumbCacheExpiry > 0 ); 00344 } 00345 00349 public static function getUserAgent() { 00350 return Http::userAgent() . " ForeignAPIRepo/" . self::VERSION; 00351 } 00352 00357 public static function httpGet( $url, $timeout = 'default', $options = array() ) { 00358 $options['timeout'] = $timeout; 00359 /* Http::get */ 00360 $url = wfExpandUrl( $url, PROTO_HTTP ); 00361 wfDebug( "ForeignAPIRepo: HTTP GET: $url\n" ); 00362 $options['method'] = "GET"; 00363 00364 if ( !isset( $options['timeout'] ) ) { 00365 $options['timeout'] = 'default'; 00366 } 00367 00368 $req = MWHttpRequest::factory( $url, $options ); 00369 $req->setUserAgent( ForeignAPIRepo::getUserAgent() ); 00370 $status = $req->execute(); 00371 00372 if ( $status->isOK() ) { 00373 return $req->getContent(); 00374 } else { 00375 return false; 00376 } 00377 } 00378 00379 function enumFiles( $callback ) { 00380 throw new MWException( 'enumFiles is not supported by ' . get_class( $this ) ); 00381 } 00382 }