Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
22.00% covered (danger)
22.00%
22 / 100
15.38% covered (danger)
15.38%
2 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
WikiFilePage
22.22% covered (danger)
22.22%
22 / 99
15.38% covered (danger)
15.38%
2 / 13
513.80
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 setFile
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 loadFile
66.67% covered (warning)
66.67%
8 / 12
0.00% covered (danger)
0.00%
0 / 1
4.59
 followRedirect
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 isRedirect
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 isLocal
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getFile
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getDuplicates
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
42
 doPurge
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
20
 getForeignCategories
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
6
 getWikiDisplayName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSourceURL
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getActionOverrides
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\Page;
8
9use MediaWiki\Actions\FileDeleteAction;
10use MediaWiki\Deferred\LinksUpdate\LinksTable;
11use MediaWiki\FileRepo\File\File;
12use MediaWiki\FileRepo\File\LocalFile;
13use MediaWiki\FileRepo\LocalRepo;
14use MediaWiki\JobQueue\Jobs\HTMLCacheUpdateJob;
15use MediaWiki\MediaWikiServices;
16use MediaWiki\Title\Title;
17use MediaWiki\Title\TitleArrayFromResult;
18use RuntimeException;
19use Wikimedia\Rdbms\FakeResultWrapper;
20
21/**
22 * Special handling for representing file pages.
23 *
24 * @ingroup Media
25 */
26class WikiFilePage extends WikiPage {
27    /** @var File|false */
28    protected $mFile = false;
29    /** @var LocalRepo|null */
30    protected $mRepo = null;
31    /** @var bool */
32    protected $mFileLoaded = false;
33    /** @var array|null */
34    protected $mDupes = null;
35
36    /**
37     * @param Title $title
38     */
39    public function __construct( $title ) {
40        parent::__construct( $title );
41        $this->mDupes = null;
42        $this->mRepo = null;
43    }
44
45    public function setFile( File $file ) {
46        $this->mFile = $file;
47        $this->mFileLoaded = true;
48    }
49
50    /**
51     * @return bool
52     */
53    protected function loadFile() {
54        $services = MediaWikiServices::getInstance();
55        if ( $this->mFileLoaded ) {
56            return true;
57        }
58
59        $this->mFile = $services->getRepoGroup()->findFile( $this->mTitle );
60        if ( !$this->mFile ) {
61            $this->mFile = $services->getRepoGroup()->getLocalRepo()
62                ->newFile( $this->mTitle );
63        }
64
65        if ( !$this->mFile instanceof File ) {
66            throw new RuntimeException( 'Expected to find file. See T250767' );
67        }
68
69        $this->mRepo = $this->mFile->getRepo();
70        $this->mFileLoaded = true;
71        return true;
72    }
73
74    /**
75     * @return bool|Title|string False, Title of in-wiki target, or string with URL
76     */
77    public function followRedirect() {
78        $this->loadFile();
79        if ( $this->mFile->isLocal() ) {
80            return parent::followRedirect();
81        }
82        $from = $this->mFile->getRedirected();
83        $to = $this->mFile->getName();
84        if ( $from === null || $from === $to ) {
85            return false;
86        }
87        return Title::makeTitle( NS_FILE, $to );
88    }
89
90    /**
91     * @return bool
92     */
93    public function isRedirect() {
94        $this->loadFile();
95        if ( $this->mFile->isLocal() ) {
96            return parent::isRedirect();
97        }
98
99        return $this->mFile->getRedirected() !== null;
100    }
101
102    /**
103     * @return bool
104     */
105    public function isLocal() {
106        $this->loadFile();
107        return $this->mFile->isLocal();
108    }
109
110    public function getFile(): File {
111        $this->loadFile();
112        return $this->mFile;
113    }
114
115    /**
116     * @return File[]|null
117     */
118    public function getDuplicates() {
119        $this->loadFile();
120        if ( $this->mDupes !== null ) {
121            return $this->mDupes;
122        }
123        $hash = $this->mFile->getSha1();
124        if ( !( $hash ) ) {
125            $this->mDupes = [];
126            return $this->mDupes;
127        }
128        $dupes = MediaWikiServices::getInstance()->getRepoGroup()->findBySha1( $hash );
129        // Remove duplicates with self and non matching file sizes
130        $self = $this->mFile->getRepoName() . ':' . $this->mFile->getName();
131        $size = $this->mFile->getSize();
132
133        /**
134         * @var File $file
135         */
136        foreach ( $dupes as $index => $file ) {
137            $key = $file->getRepoName() . ':' . $file->getName();
138            if ( $key === $self || $file->getSize() != $size ) {
139                unset( $dupes[$index] );
140            }
141        }
142        $this->mDupes = $dupes;
143        return $this->mDupes;
144    }
145
146    /**
147     * Override handling of action=purge
148     * @return bool
149     */
150    public function doPurge() {
151        $this->loadFile();
152
153        if ( $this->mFile->exists() ) {
154            wfDebug( 'ImagePage::doPurge purging ' . $this->mFile->getName() );
155            $job = HTMLCacheUpdateJob::newForBacklinks(
156                $this->mTitle,
157                'imagelinks',
158                [ 'causeAction' => 'file-purge' ]
159            );
160            MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $job );
161        } else {
162            wfDebug( 'ImagePage::doPurge no image for '
163                . $this->mFile->getName() . "; limiting purge to cache only" );
164        }
165
166        // even if the file supposedly doesn't exist, force any cached information
167        // to be updated (in case the cached information is wrong)
168
169        // Purge current version and its thumbnails
170        $this->mFile->purgeCache( [ 'forThumbRefresh' => true ] );
171
172        // Purge the old versions and their thumbnails
173        foreach ( $this->mFile->getHistory() as $oldFile ) {
174            $oldFile->purgeCache( [ 'forThumbRefresh' => true ] );
175        }
176
177        if ( $this->mRepo ) {
178            // Purge redirect cache
179            $this->mRepo->invalidateImageRedirect( $this->mTitle );
180        }
181
182        return parent::doPurge();
183    }
184
185    /**
186     * Get the categories this file is a member of on the wiki where it was uploaded.
187     * For local files, this is the same as getCategories().
188     * For foreign API files (InstantCommons), this is not supported currently.
189     * Results will include hidden categories.
190     *
191     * @return TitleArrayFromResult
192     * @since 1.23
193     */
194    public function getForeignCategories() {
195        $this->loadFile();
196        $title = $this->mTitle;
197        $file = $this->mFile;
198
199        $services = MediaWikiServices::getInstance();
200        $titleFactory = $services->getTitleFactory();
201        $lbFactory = $services->getDBLoadBalancerFactory();
202
203        if ( !$file instanceof LocalFile ) {
204            wfDebug( __METHOD__ . " is not supported for this file" );
205            return $titleFactory->newTitleArrayFromResult( new FakeResultWrapper( [] ) );
206        }
207
208        /** @var LocalRepo $repo */
209        $repo = $file->getRepo();
210        $wikiId = $repo->getDbDomain();
211        $dbr = $lbFactory->getRemoteReplicaDatabase( $wikiId, LinksTable::VIRTUAL_DOMAIN );
212        $res = $dbr->newSelectQueryBuilder()
213            ->select( [ 'page_title' => 'lt_title', 'page_namespace' => (string)NS_CATEGORY ] )
214            ->from( 'page' )
215            ->join( 'categorylinks', null, 'page_id = cl_from' )
216            ->join( 'linktarget', null, 'cl_target_id = lt_id' )
217            ->where( [ 'page_namespace' => $title->getNamespace(), 'page_title' => $title->getDBkey(), ] )
218            ->caller( __METHOD__ )
219            ->fetchResultSet();
220
221        return $titleFactory->newTitleArrayFromResult( $res );
222    }
223
224    /**
225     * @since 1.28
226     * @return string
227     */
228    public function getWikiDisplayName() {
229        return $this->getFile()->getRepo()->getDisplayName();
230    }
231
232    /**
233     * @since 1.28
234     * @return string
235     */
236    public function getSourceURL() {
237        return $this->getFile()->getDescriptionUrl();
238    }
239
240    /**
241     * @inheritDoc
242     */
243    public function getActionOverrides() {
244        $file = $this->getFile();
245        if ( $file->exists() && $file->isLocal() && !$file->getRedirected() ) {
246            // Would be an actual file deletion
247            return [ 'delete' => [
248                'class' => FileDeleteAction::class,
249                'services' => [
250                    'RepoGroup',
251                    'UrlUtils',
252                ],
253            ] ] + parent::getActionOverrides();
254        }
255        // It should use the normal article deletion interface
256        return parent::getActionOverrides();
257    }
258}
259
260/** @deprecated class alias since 1.44 */
261class_alias( WikiFilePage::class, 'WikiFilePage' );