3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
21 use MediaWiki\Actions\FileDeleteAction
;
22 use MediaWiki\MediaWikiServices
;
23 use MediaWiki\Title\Title
;
24 use MediaWiki\Title\TitleArrayFromResult
;
25 use Wikimedia\Rdbms\FakeResultWrapper
;
28 * Special handling for representing file pages.
32 class WikiFilePage
extends WikiPage
{
33 /** @var File|false */
34 protected $mFile = false;
35 /** @var LocalRepo|null */
36 protected $mRepo = null;
38 protected $mFileLoaded = false;
39 /** @var array|null */
40 protected $mDupes = null;
45 public function __construct( $title ) {
46 parent
::__construct( $title );
54 public function setFile( File
$file ) {
56 $this->mFileLoaded
= true;
62 protected function loadFile() {
63 $services = MediaWikiServices
::getInstance();
64 if ( $this->mFileLoaded
) {
68 $this->mFile
= $services->getRepoGroup()->findFile( $this->mTitle
);
69 if ( !$this->mFile
) {
70 $this->mFile
= $services->getRepoGroup()->getLocalRepo()
71 ->newFile( $this->mTitle
);
74 if ( !$this->mFile
instanceof File
) {
75 throw new RuntimeException( 'Expected to find file. See T250767' );
78 $this->mRepo
= $this->mFile
->getRepo();
79 $this->mFileLoaded
= true;
84 * @return bool|Title|string False, Title of in-wiki target, or string with URL
86 public function followRedirect() {
88 if ( $this->mFile
->isLocal() ) {
89 return parent
::followRedirect();
91 $from = $this->mFile
->getRedirected();
92 $to = $this->mFile
->getName();
93 if ( $from === null ||
$from === $to ) {
96 return Title
::makeTitle( NS_FILE
, $to );
102 public function isRedirect() {
104 if ( $this->mFile
->isLocal() ) {
105 return parent
::isRedirect();
108 return $this->mFile
->getRedirected() !== null;
114 public function isLocal() {
116 return $this->mFile
->isLocal();
122 public function getFile(): File
{
128 * @return File[]|null
130 public function getDuplicates() {
132 if ( $this->mDupes
!== null ) {
133 return $this->mDupes
;
135 $hash = $this->mFile
->getSha1();
138 return $this->mDupes
;
140 $dupes = MediaWikiServices
::getInstance()->getRepoGroup()->findBySha1( $hash );
141 // Remove duplicates with self and non matching file sizes
142 $self = $this->mFile
->getRepoName() . ':' . $this->mFile
->getName();
143 $size = $this->mFile
->getSize();
148 foreach ( $dupes as $index => $file ) {
149 $key = $file->getRepoName() . ':' . $file->getName();
150 if ( $key === $self ||
$file->getSize() != $size ) {
151 unset( $dupes[$index] );
154 $this->mDupes
= $dupes;
155 return $this->mDupes
;
159 * Override handling of action=purge
162 public function doPurge() {
165 if ( $this->mFile
->exists() ) {
166 wfDebug( 'ImagePage::doPurge purging ' . $this->mFile
->getName() );
167 $job = HTMLCacheUpdateJob
::newForBacklinks(
170 [ 'causeAction' => 'file-purge' ]
172 MediaWikiServices
::getInstance()->getJobQueueGroup()->lazyPush( $job );
174 wfDebug( 'ImagePage::doPurge no image for '
175 . $this->mFile
->getName() . "; limiting purge to cache only" );
178 // even if the file supposedly doesn't exist, force any cached information
179 // to be updated (in case the cached information is wrong)
181 // Purge current version and its thumbnails
182 $this->mFile
->purgeCache( [ 'forThumbRefresh' => true ] );
184 // Purge the old versions and their thumbnails
185 foreach ( $this->mFile
->getHistory() as $oldFile ) {
186 $oldFile->purgeCache( [ 'forThumbRefresh' => true ] );
189 if ( $this->mRepo
) {
190 // Purge redirect cache
191 $this->mRepo
->invalidateImageRedirect( $this->mTitle
);
194 return parent
::doPurge();
198 * Get the categories this file is a member of on the wiki where it was uploaded.
199 * For local files, this is the same as getCategories().
200 * For foreign API files (InstantCommons), this is not supported currently.
201 * Results will include hidden categories.
203 * @return TitleArrayFromResult
206 public function getForeignCategories() {
208 $title = $this->mTitle
;
209 $file = $this->mFile
;
210 $titleFactory = MediaWikiServices
::getInstance()->getTitleFactory();
212 if ( !$file instanceof LocalFile
) {
213 wfDebug( __METHOD__
. " is not supported for this file" );
214 return $titleFactory->newTitleArrayFromResult( new FakeResultWrapper( [] ) );
217 /** @var LocalRepo $repo */
218 $repo = $file->getRepo();
219 $dbr = $repo->getReplicaDB();
221 $res = $dbr->newSelectQueryBuilder()
222 ->select( [ 'page_title' => 'cl_to', 'page_namespace' => (string)NS_CATEGORY
] )
224 ->join( 'categorylinks', null, 'page_id = cl_from' )
225 ->where( [ 'page_namespace' => $title->getNamespace(), 'page_title' => $title->getDBkey(), ] )
226 ->caller( __METHOD__
)->fetchResultSet();
228 return $titleFactory->newTitleArrayFromResult( $res );
235 public function getWikiDisplayName() {
236 return $this->getFile()->getRepo()->getDisplayName();
243 public function getSourceURL() {
244 return $this->getFile()->getDescriptionUrl();
250 public function getActionOverrides() {
251 $file = $this->getFile();
252 if ( $file->exists() && $file->isLocal() && !$file->getRedirected() ) {
253 // Would be an actual file deletion
254 return [ 'delete' => FileDeleteAction
::class ] + parent
::getActionOverrides();
256 // It should use the normal article deletion interface
257 return parent
::getActionOverrides();