Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
44.44% covered (danger)
44.44%
28 / 63
33.33% covered (danger)
33.33%
5 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
MediaTransformOutput
45.16% covered (danger)
45.16%
28 / 62
33.33% covered (danger)
33.33%
5 / 15
289.84
0.00% covered (danger)
0.00%
0 / 1
 getWidth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHeight
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFile
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExtension
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 getUrl
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 getStoragePath
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setStoragePath
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 toHtml
n/a
0 / 0
n/a
0 / 0
0
 isError
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasFile
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
12
 fileIsSource
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 getLocalCopyPath
44.44% covered (danger)
44.44%
4 / 9
0.00% covered (danger)
0.00%
0 / 1
9.29
 streamFileWithStatus
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
6
 streamFile
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 linkWrap
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 getDescLinkAttribs
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
72
1<?php
2
3namespace MediaWiki\Media;
4
5/**
6 * Base class for the output of file transformation methods.
7 *
8 * @license GPL-2.0-or-later
9 * @file
10 * @ingroup Media
11 */
12
13use MediaWiki\FileRepo\File\File;
14use MediaWiki\Html\Html;
15use MediaWiki\Status\Status;
16use Wikimedia\FileBackend\FileBackend;
17use Wikimedia\FileBackend\HTTPFileStreamer;
18
19/**
20 * Base class for the output of MediaHandler::doTransform() and File::transform().
21 *
22 * @stable to extend
23 * @ingroup Media
24 */
25abstract class MediaTransformOutput {
26    /** @var array Associative array mapping optional supplementary image files
27     *  from pixel density (eg 1.5 or 2) to additional URLs.
28     */
29    public $responsiveUrls = [];
30
31    /** @var File */
32    protected $file;
33
34    /** @var int Image width */
35    protected $width;
36
37    /** @var int Image height */
38    protected $height;
39
40    /** @var string|false URL path to the thumb */
41    protected $url;
42
43    /** @var string|false */
44    protected $page;
45
46    /** @var string|null|false Filesystem path to the thumb */
47    protected $path;
48
49    /** @var string|false Language code, false if not set */
50    protected $lang;
51
52    /** @var string|false Permanent storage path */
53    protected $storagePath = false;
54
55    /**
56     * @return int Width of the output box
57     */
58    public function getWidth() {
59        return $this->width;
60    }
61
62    /**
63     * @return int Height of the output box
64     */
65    public function getHeight() {
66        return $this->height;
67    }
68
69    /**
70     * @return File
71     */
72    public function getFile() {
73        return $this->file;
74    }
75
76    /**
77     * Get the final extension of the thumbnail.
78     * Returns false for scripted transformations.
79     * @stable to override
80     *
81     * @return string|false
82     */
83    public function getExtension() {
84        return $this->path ? FileBackend::extensionFromPath( $this->path ) : false;
85    }
86
87    /**
88     * @stable to override
89     *
90     * @return string|false The thumbnail URL
91     */
92    public function getUrl() {
93        if ( $this->url === false ) {
94            return false;
95        }
96
97        return $this->getFile()->appendRequestProvenance( $this->url, [
98            'format' => 'thumbnail',
99        ] );
100    }
101
102    /**
103     * @stable to override
104     *
105     * @return string|false The permanent thumbnail storage path
106     */
107    public function getStoragePath() {
108        return $this->storagePath;
109    }
110
111    /**
112     * @stable to override
113     *
114     * @param string $storagePath The permanent storage path
115     * @return void
116     */
117    public function setStoragePath( $storagePath ) {
118        $this->storagePath = $storagePath;
119        if ( $this->path === false ) {
120            $this->path = $storagePath;
121        }
122    }
123
124    /**
125     * Fetch HTML for this transform output
126     *
127     * @param array $options Associative array of options. Boolean options
128     *   should be indicated with a value of true for true, and false or
129     *   absent for false.
130     *
131     *   - alt        : Alternate text or caption
132     *   - desc-link  : Boolean, show a description link
133     *   - file-link  : Boolean, show a file download link
134     *   - custom-url-link   : Custom URL to link to
135     *   - custom-title-link : Custom Title object to link to
136     *   - valign     : vertical-align property, if the output is an inline element
137     *   - img-class  : Class applied to the `<img>` tag, if there is such a tag
138     *
139     * For images, desc-link and file-link are implemented as a click-through. For
140     * sounds and videos, they may be displayed in other ways.
141     *
142     * @return string
143     */
144    abstract public function toHtml( $options = [] );
145
146    /**
147     * This will be overridden to return true in error classes
148     * @return bool
149     */
150    public function isError() {
151        return false;
152    }
153
154    /**
155     * Check if an output thumbnail file actually exists.
156     *
157     * This will return false if there was an error, the
158     * thumbnail is to be handled client-side only, or if
159     * transformation was deferred via TRANSFORM_LATER.
160     * This file may exist as a new file in /tmp, a file
161     * in permanent storage, or even refer to the original.
162     *
163     * @return bool
164     */
165    public function hasFile() {
166        // If TRANSFORM_LATER, $this->path will be false.
167        // Note: a null path means "use the source file".
168        return ( !$this->isError() && ( $this->path || $this->path === null ) );
169    }
170
171    /**
172     * Check if the output thumbnail is the same as the source.
173     * This can occur if the requested width was bigger than the source.
174     *
175     * @return bool
176     */
177    public function fileIsSource() {
178        return ( !$this->isError() && $this->path === null );
179    }
180
181    /**
182     * Get the path of a file system copy of the thumbnail.
183     * Callers should never write to this path.
184     *
185     * @return string|false Returns false if there isn't one
186     */
187    public function getLocalCopyPath() {
188        if ( $this->isError() ) {
189            return false;
190        }
191
192        if ( $this->path === null ) {
193            // assume thumb was not scaled
194            return $this->file->getLocalRefPath();
195        }
196        if ( FileBackend::isStoragePath( $this->path ) ) {
197            $be = $this->file->getRepo()->getBackend();
198            // The temp file is process-cached by FileBackend
199            $fsFile = $be->getLocalReference( [ 'src' => $this->path ] );
200
201            return $fsFile ? $fsFile->getPath() : false;
202        }
203        // may return false
204        return $this->path;
205    }
206
207    /**
208     * Stream the file if there were no errors
209     *
210     * @param array $headers Additional HTTP headers to send on success
211     * @return Status
212     * @since 1.27
213     */
214    public function streamFileWithStatus( $headers = [] ) {
215        if ( !$this->path ) {
216            return Status::newFatal( 'backend-fail-stream', '<no path>' );
217        }
218
219        $repo = $this->file->getRepo();
220
221        if ( $repo && FileBackend::isStoragePath( $this->path ) ) {
222            return Status::wrap(
223                $repo->getBackend()->streamFile(
224                    [ 'src' => $this->path, 'headers' => $headers, ]
225                )
226            );
227        }
228
229        $streamer = new HTTPFileStreamer(
230            $this->getLocalCopyPath(),
231            $repo ? $repo->getBackend()->getStreamerOptions() : []
232        );
233
234        $success = $streamer->stream( $headers );
235
236        return $success ? Status::newGood()
237            : Status::newFatal( 'backend-fail-stream', $this->path );
238    }
239
240    /**
241     * Stream the file if there were no errors
242     *
243     * @deprecated since 1.26, use streamFileWithStatus; hard-deprecated since 1.47
244     * @param array $headers Additional HTTP headers to send on success
245     * @return bool Success
246     */
247    public function streamFile( $headers = [] ) {
248        wfDeprecated( __METHOD__, '1.26' );
249        return $this->streamFileWithStatus( $headers )->isOK();
250    }
251
252    /**
253     * Wrap some XHTML text in an anchor tag with the given attributes
254     * or, fallback to a span in the absence thereof.
255     *
256     * @param array $linkAttribs
257     * @param string $contents
258     * @return string
259     */
260    protected function linkWrap( $linkAttribs, $contents ) {
261        if ( isset( $linkAttribs['href'] ) ) {
262            return Html::rawElement( 'a', $linkAttribs, $contents );
263        }
264        return Html::rawElement( 'span', $linkAttribs ?: [], $contents );
265    }
266
267    /**
268     * @param string|null $title
269     * @param string|array $params Query parameters to add
270     * @return array
271     */
272    public function getDescLinkAttribs( $title = null, $params = [] ) {
273        if ( is_array( $params ) ) {
274            $query = $params;
275        } else {
276            $query = [];
277        }
278        if ( $this->page && $this->page !== 1 ) {
279            $query['page'] = $this->page;
280        }
281        if ( $this->lang ) {
282            $query['lang'] = $this->lang;
283        }
284
285        if ( is_string( $params ) && $params !== '' ) {
286            $query = $params . '&' . wfArrayToCgi( $query );
287        }
288
289        $attribs = [
290            'href' => $this->file->getTitle()->getLocalURL( $query ),
291            'class' => 'mw-file-description',
292        ];
293
294        if ( $title ) {
295            $attribs['title'] = $title;
296        }
297
298        return $attribs;
299    }
300}
301
302/** @deprecated class alias since 1.46 */
303class_alias( MediaTransformOutput::class, 'MediaTransformOutput' );