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