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