[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Local repository that stores files in the local filesystem and registers them 4 * in the wiki's own database. 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License along 17 * with this program; if not, write to the Free Software Foundation, Inc., 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 * http://www.gnu.org/copyleft/gpl.html 20 * 21 * @file 22 * @ingroup FileRepo 23 */ 24 25 /** 26 * A repository that stores files in the local filesystem and registers them 27 * in the wiki's own database. This is the most commonly used repository class. 28 * 29 * @ingroup FileRepo 30 */ 31 class LocalRepo extends FileRepo { 32 /** @var array */ 33 protected $fileFactory = array( 'LocalFile', 'newFromTitle' ); 34 35 /** @var array */ 36 protected $fileFactoryKey = array( 'LocalFile', 'newFromKey' ); 37 38 /** @var array */ 39 protected $fileFromRowFactory = array( 'LocalFile', 'newFromRow' ); 40 41 /** @var array */ 42 protected $oldFileFromRowFactory = array( 'OldLocalFile', 'newFromRow' ); 43 44 /** @var array */ 45 protected $oldFileFactory = array( 'OldLocalFile', 'newFromTitle' ); 46 47 /** @var array */ 48 protected $oldFileFactoryKey = array( 'OldLocalFile', 'newFromKey' ); 49 50 /** 51 * @throws MWException 52 * @param stdClass $row 53 * @return LocalFile 54 */ 55 function newFileFromRow( $row ) { 56 if ( isset( $row->img_name ) ) { 57 return call_user_func( $this->fileFromRowFactory, $row, $this ); 58 } elseif ( isset( $row->oi_name ) ) { 59 return call_user_func( $this->oldFileFromRowFactory, $row, $this ); 60 } else { 61 throw new MWException( __METHOD__ . ': invalid row' ); 62 } 63 } 64 65 /** 66 * @param Title $title 67 * @param string $archiveName 68 * @return OldLocalFile 69 */ 70 function newFromArchiveName( $title, $archiveName ) { 71 return OldLocalFile::newFromArchiveName( $title, $this, $archiveName ); 72 } 73 74 /** 75 * Delete files in the deleted directory if they are not referenced in the 76 * filearchive table. This needs to be done in the repo because it needs to 77 * interleave database locks with file operations, which is potentially a 78 * remote operation. 79 * 80 * @param array $storageKeys 81 * 82 * @return FileRepoStatus 83 */ 84 function cleanupDeletedBatch( array $storageKeys ) { 85 $backend = $this->backend; // convenience 86 $root = $this->getZonePath( 'deleted' ); 87 $dbw = $this->getMasterDB(); 88 $status = $this->newGood(); 89 $storageKeys = array_unique( $storageKeys ); 90 foreach ( $storageKeys as $key ) { 91 $hashPath = $this->getDeletedHashPath( $key ); 92 $path = "$root/$hashPath$key"; 93 $dbw->begin( __METHOD__ ); 94 // Check for usage in deleted/hidden files and preemptively 95 // lock the key to avoid any future use until we are finished. 96 $deleted = $this->deletedFileHasKey( $key, 'lock' ); 97 $hidden = $this->hiddenFileHasKey( $key, 'lock' ); 98 if ( !$deleted && !$hidden ) { // not in use now 99 wfDebug( __METHOD__ . ": deleting $key\n" ); 100 $op = array( 'op' => 'delete', 'src' => $path ); 101 if ( !$backend->doOperation( $op )->isOK() ) { 102 $status->error( 'undelete-cleanup-error', $path ); 103 $status->failCount++; 104 } 105 } else { 106 wfDebug( __METHOD__ . ": $key still in use\n" ); 107 $status->successCount++; 108 } 109 $dbw->commit( __METHOD__ ); 110 } 111 112 return $status; 113 } 114 115 /** 116 * Check if a deleted (filearchive) file has this sha1 key 117 * 118 * @param string $key File storage key (base-36 sha1 key with file extension) 119 * @param string|null $lock Use "lock" to lock the row via FOR UPDATE 120 * @return bool File with this key is in use 121 */ 122 protected function deletedFileHasKey( $key, $lock = null ) { 123 $options = ( $lock === 'lock' ) ? array( 'FOR UPDATE' ) : array(); 124 125 $dbw = $this->getMasterDB(); 126 127 return (bool)$dbw->selectField( 'filearchive', '1', 128 array( 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ), 129 __METHOD__, $options 130 ); 131 } 132 133 /** 134 * Check if a hidden (revision delete) file has this sha1 key 135 * 136 * @param string $key File storage key (base-36 sha1 key with file extension) 137 * @param string|null $lock Use "lock" to lock the row via FOR UPDATE 138 * @return bool File with this key is in use 139 */ 140 protected function hiddenFileHasKey( $key, $lock = null ) { 141 $options = ( $lock === 'lock' ) ? array( 'FOR UPDATE' ) : array(); 142 143 $sha1 = self::getHashFromKey( $key ); 144 $ext = File::normalizeExtension( substr( $key, strcspn( $key, '.' ) + 1 ) ); 145 146 $dbw = $this->getMasterDB(); 147 148 return (bool)$dbw->selectField( 'oldimage', '1', 149 array( 'oi_sha1' => $sha1, 150 'oi_archive_name ' . $dbw->buildLike( $dbw->anyString(), ".$ext" ), 151 $dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE ), 152 __METHOD__, $options 153 ); 154 } 155 156 /** 157 * Gets the SHA1 hash from a storage key 158 * 159 * @param string $key 160 * @return string 161 */ 162 public static function getHashFromKey( $key ) { 163 return strtok( $key, '.' ); 164 } 165 166 /** 167 * Checks if there is a redirect named as $title 168 * 169 * @param Title $title Title of file 170 * @return bool|Title 171 */ 172 function checkRedirect( Title $title ) { 173 global $wgMemc; 174 175 $title = File::normalizeTitle( $title, 'exception' ); 176 177 $memcKey = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) ); 178 if ( $memcKey === false ) { 179 $memcKey = $this->getLocalCacheKey( 'image_redirect', md5( $title->getDBkey() ) ); 180 $expiry = 300; // no invalidation, 5 minutes 181 } else { 182 $expiry = 86400; // has invalidation, 1 day 183 } 184 $cachedValue = $wgMemc->get( $memcKey ); 185 if ( $cachedValue === ' ' || $cachedValue === '' ) { 186 // Does not exist 187 return false; 188 } elseif ( strval( $cachedValue ) !== '' && $cachedValue !== ' PURGED' ) { 189 return Title::newFromText( $cachedValue, NS_FILE ); 190 } // else $cachedValue is false or null: cache miss 191 192 $id = $this->getArticleID( $title ); 193 if ( !$id ) { 194 $wgMemc->add( $memcKey, " ", $expiry ); 195 196 return false; 197 } 198 $dbr = $this->getSlaveDB(); 199 $row = $dbr->selectRow( 200 'redirect', 201 array( 'rd_title', 'rd_namespace' ), 202 array( 'rd_from' => $id ), 203 __METHOD__ 204 ); 205 206 if ( $row && $row->rd_namespace == NS_FILE ) { 207 $targetTitle = Title::makeTitle( $row->rd_namespace, $row->rd_title ); 208 $wgMemc->add( $memcKey, $targetTitle->getDBkey(), $expiry ); 209 210 return $targetTitle; 211 } else { 212 $wgMemc->add( $memcKey, '', $expiry ); 213 214 return false; 215 } 216 } 217 218 /** 219 * Function link Title::getArticleID(). 220 * We can't say Title object, what database it should use, so we duplicate that function here. 221 * 222 * @param Title $title 223 * @return bool|int|mixed 224 */ 225 protected function getArticleID( $title ) { 226 if ( !$title instanceof Title ) { 227 return 0; 228 } 229 $dbr = $this->getSlaveDB(); 230 $id = $dbr->selectField( 231 'page', // Table 232 'page_id', //Field 233 array( //Conditions 234 'page_namespace' => $title->getNamespace(), 235 'page_title' => $title->getDBkey(), 236 ), 237 __METHOD__ //Function name 238 ); 239 240 return $id; 241 } 242 243 public function findFiles( array $items, $flags = 0 ) { 244 $finalFiles = array(); // map of (DB key => corresponding File) for matches 245 246 $searchSet = array(); // map of (normalized DB key => search params) 247 foreach ( $items as $item ) { 248 if ( is_array( $item ) ) { 249 $title = File::normalizeTitle( $item['title'] ); 250 if ( $title ) { 251 $searchSet[$title->getDBkey()] = $item; 252 } 253 } else { 254 $title = File::normalizeTitle( $item ); 255 if ( $title ) { 256 $searchSet[$title->getDBkey()] = array(); 257 } 258 } 259 } 260 261 $fileMatchesSearch = function ( File $file, array $search ) { 262 // Note: file name comparison done elsewhere (to handle redirects) 263 $user = ( !empty( $search['private'] ) && $search['private'] instanceof User ) 264 ? $search['private'] 265 : null; 266 267 return ( 268 $file->exists() && 269 ( 270 ( empty( $search['time'] ) && !$file->isOld() ) || 271 ( !empty( $search['time'] ) && $search['time'] === $file->getTimestamp() ) 272 ) && 273 ( !empty( $search['private'] ) || !$file->isDeleted( File::DELETED_FILE ) ) && 274 $file->userCan( File::DELETED_FILE, $user ) 275 ); 276 }; 277 278 $repo = $this; 279 $applyMatchingFiles = function ( ResultWrapper $res, &$searchSet, &$finalFiles ) 280 use ( $repo, $fileMatchesSearch, $flags ) 281 { 282 global $wgContLang; 283 $info = $repo->getInfo(); 284 foreach ( $res as $row ) { 285 $file = $repo->newFileFromRow( $row ); 286 // There must have been a search for this DB key, but this has to handle the 287 // cases were title capitalization is different on the client and repo wikis. 288 $dbKeysLook = array( str_replace( ' ', '_', $file->getName() ) ); 289 if ( !empty( $info['initialCapital'] ) ) { 290 // Search keys for "hi.png" and "Hi.png" should use the "Hi.png file" 291 $dbKeysLook[] = $wgContLang->lcfirst( $file->getName() ); 292 } 293 foreach ( $dbKeysLook as $dbKey ) { 294 if ( isset( $searchSet[$dbKey] ) 295 && $fileMatchesSearch( $file, $searchSet[$dbKey] ) 296 ) { 297 $finalFiles[$dbKey] = ( $flags & FileRepo::NAME_AND_TIME_ONLY ) 298 ? array( 'title' => $dbKey, 'timestamp' => $file->getTimestamp() ) 299 : $file; 300 unset( $searchSet[$dbKey] ); 301 } 302 } 303 } 304 }; 305 306 $dbr = $this->getSlaveDB(); 307 308 // Query image table 309 $imgNames = array(); 310 foreach ( array_keys( $searchSet ) as $dbKey ) { 311 $imgNames[] = $this->getNameFromTitle( File::normalizeTitle( $dbKey ) ); 312 } 313 314 if ( count( $imgNames ) ) { 315 $res = $dbr->select( 'image', 316 LocalFile::selectFields(), array( 'img_name' => $imgNames ), __METHOD__ ); 317 $applyMatchingFiles( $res, $searchSet, $finalFiles ); 318 } 319 320 // Query old image table 321 $oiConds = array(); // WHERE clause array for each file 322 foreach ( $searchSet as $dbKey => $search ) { 323 if ( isset( $search['time'] ) ) { 324 $oiConds[] = $dbr->makeList( 325 array( 326 'oi_name' => $this->getNameFromTitle( File::normalizeTitle( $dbKey ) ), 327 'oi_timestamp' => $dbr->timestamp( $search['time'] ) 328 ), 329 LIST_AND 330 ); 331 } 332 } 333 334 if ( count( $oiConds ) ) { 335 $res = $dbr->select( 'oldimage', 336 OldLocalFile::selectFields(), $dbr->makeList( $oiConds, LIST_OR ), __METHOD__ ); 337 $applyMatchingFiles( $res, $searchSet, $finalFiles ); 338 } 339 340 // Check for redirects... 341 foreach ( $searchSet as $dbKey => $search ) { 342 if ( !empty( $search['ignoreRedirect'] ) ) { 343 continue; 344 } 345 346 $title = File::normalizeTitle( $dbKey ); 347 $redir = $this->checkRedirect( $title ); // hopefully hits memcached 348 349 if ( $redir && $redir->getNamespace() == NS_FILE ) { 350 $file = $this->newFile( $redir ); 351 if ( $file && $fileMatchesSearch( $file, $search ) ) { 352 $file->redirectedFrom( $title->getDBkey() ); 353 if ( $flags & FileRepo::NAME_AND_TIME_ONLY ) { 354 $finalFiles[$dbKey] = array( 355 'title' => $file->getTitle()->getDBkey(), 356 'timestamp' => $file->getTimestamp() 357 ); 358 } else { 359 $finalFiles[$dbKey] = $file; 360 } 361 } 362 } 363 } 364 365 return $finalFiles; 366 } 367 368 /** 369 * Get an array or iterator of file objects for files that have a given 370 * SHA-1 content hash. 371 * 372 * @param string $hash A sha1 hash to look for 373 * @return File[] 374 */ 375 function findBySha1( $hash ) { 376 $dbr = $this->getSlaveDB(); 377 $res = $dbr->select( 378 'image', 379 LocalFile::selectFields(), 380 array( 'img_sha1' => $hash ), 381 __METHOD__, 382 array( 'ORDER BY' => 'img_name' ) 383 ); 384 385 $result = array(); 386 foreach ( $res as $row ) { 387 $result[] = $this->newFileFromRow( $row ); 388 } 389 $res->free(); 390 391 return $result; 392 } 393 394 /** 395 * Get an array of arrays or iterators of file objects for files that 396 * have the given SHA-1 content hashes. 397 * 398 * Overrides generic implementation in FileRepo for performance reason 399 * 400 * @param array $hashes An array of hashes 401 * @return array An Array of arrays or iterators of file objects and the hash as key 402 */ 403 function findBySha1s( array $hashes ) { 404 if ( !count( $hashes ) ) { 405 return array(); //empty parameter 406 } 407 408 $dbr = $this->getSlaveDB(); 409 $res = $dbr->select( 410 'image', 411 LocalFile::selectFields(), 412 array( 'img_sha1' => $hashes ), 413 __METHOD__, 414 array( 'ORDER BY' => 'img_name' ) 415 ); 416 417 $result = array(); 418 foreach ( $res as $row ) { 419 $file = $this->newFileFromRow( $row ); 420 $result[$file->getSha1()][] = $file; 421 } 422 $res->free(); 423 424 return $result; 425 } 426 427 /** 428 * Return an array of files where the name starts with $prefix. 429 * 430 * @param string $prefix The prefix to search for 431 * @param int $limit The maximum amount of files to return 432 * @return array 433 */ 434 public function findFilesByPrefix( $prefix, $limit ) { 435 $selectOptions = array( 'ORDER BY' => 'img_name', 'LIMIT' => intval( $limit ) ); 436 437 // Query database 438 $dbr = $this->getSlaveDB(); 439 $res = $dbr->select( 440 'image', 441 LocalFile::selectFields(), 442 'img_name ' . $dbr->buildLike( $prefix, $dbr->anyString() ), 443 __METHOD__, 444 $selectOptions 445 ); 446 447 // Build file objects 448 $files = array(); 449 foreach ( $res as $row ) { 450 $files[] = $this->newFileFromRow( $row ); 451 } 452 453 return $files; 454 } 455 456 /** 457 * Get a connection to the slave DB 458 * @return DatabaseBase 459 */ 460 function getSlaveDB() { 461 return wfGetDB( DB_SLAVE ); 462 } 463 464 /** 465 * Get a connection to the master DB 466 * @return DatabaseBase 467 */ 468 function getMasterDB() { 469 return wfGetDB( DB_MASTER ); 470 } 471 472 /** 473 * Get a key on the primary cache for this repository. 474 * Returns false if the repository's cache is not accessible at this site. 475 * The parameters are the parts of the key, as for wfMemcKey(). 476 * 477 * @return string 478 */ 479 function getSharedCacheKey( /*...*/ ) { 480 $args = func_get_args(); 481 482 return call_user_func_array( 'wfMemcKey', $args ); 483 } 484 485 /** 486 * Invalidates image redirect cache related to that image 487 * 488 * @param Title $title Title of page 489 * @return void 490 */ 491 function invalidateImageRedirect( Title $title ) { 492 global $wgMemc; 493 $memcKey = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) ); 494 if ( $memcKey ) { 495 // Set a temporary value for the cache key, to ensure 496 // that this value stays purged long enough so that 497 // it isn't refreshed with a stale value due to a 498 // lagged slave. 499 $wgMemc->set( $memcKey, ' PURGED', 12 ); 500 } 501 } 502 503 /** 504 * Return information about the repository. 505 * 506 * @return array 507 * @since 1.22 508 */ 509 function getInfo() { 510 global $wgFavicon; 511 512 return array_merge( parent::getInfo(), array( 513 'favicon' => wfExpandUrl( $wgFavicon ), 514 ) ); 515 } 516 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |