Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
43.10% covered (danger)
43.10%
25 / 58
40.00% covered (danger)
40.00%
6 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
MediaTransformOutput
43.86% covered (danger)
43.86%
25 / 57
40.00% covered (danger)
40.00%
6 / 15
293.50
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
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 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 / 1
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        return $this->url;
94    }
95
96    /**
97     * @stable to override
98     *
99     * @return string|false The permanent thumbnail storage path
100     */
101    public function getStoragePath() {
102        return $this->storagePath;
103    }
104
105    /**
106     * @stable to override
107     *
108     * @param string $storagePath The permanent storage path
109     * @return void
110     */
111    public function setStoragePath( $storagePath ) {
112        $this->storagePath = $storagePath;
113        if ( $this->path === false ) {
114            $this->path = $storagePath;
115        }
116    }
117
118    /**
119     * Fetch HTML for this transform output
120     *
121     * @param array $options Associative array of options. Boolean options
122     *     should be indicated with a value of true for true, and false or
123     *     absent for false.
124     *
125     *     alt          Alternate text or caption
126     *     desc-link    Boolean, show a description link
127     *     file-link    Boolean, show a file download link
128     *     custom-url-link    Custom URL to link to
129     *     custom-title-link  Custom Title object to link to
130     *     valign       vertical-align property, if the output is an inline element
131     *     img-class    Class applied to the "<img>" tag, if there is such a tag
132     *
133     * For images, desc-link and file-link are implemented as a click-through. For
134     * sounds and videos, they may be displayed in other ways.
135     *
136     * @return string
137     */
138    abstract public function toHtml( $options = [] );
139
140    /**
141     * This will be overridden to return true in error classes
142     * @return bool
143     */
144    public function isError() {
145        return false;
146    }
147
148    /**
149     * Check if an output thumbnail file actually exists.
150     *
151     * This will return false if there was an error, the
152     * thumbnail is to be handled client-side only, or if
153     * transformation was deferred via TRANSFORM_LATER.
154     * This file may exist as a new file in /tmp, a file
155     * in permanent storage, or even refer to the original.
156     *
157     * @return bool
158     */
159    public function hasFile() {
160        // If TRANSFORM_LATER, $this->path will be false.
161        // Note: a null path means "use the source file".
162        return ( !$this->isError() && ( $this->path || $this->path === null ) );
163    }
164
165    /**
166     * Check if the output thumbnail is the same as the source.
167     * This can occur if the requested width was bigger than the source.
168     *
169     * @return bool
170     */
171    public function fileIsSource() {
172        return ( !$this->isError() && $this->path === null );
173    }
174
175    /**
176     * Get the path of a file system copy of the thumbnail.
177     * Callers should never write to this path.
178     *
179     * @return string|false Returns false if there isn't one
180     */
181    public function getLocalCopyPath() {
182        if ( $this->isError() ) {
183            return false;
184        }
185
186        if ( $this->path === null ) {
187            // assume thumb was not scaled
188            return $this->file->getLocalRefPath();
189        }
190        if ( FileBackend::isStoragePath( $this->path ) ) {
191            $be = $this->file->getRepo()->getBackend();
192            // The temp file is process-cached by FileBackend
193            $fsFile = $be->getLocalReference( [ 'src' => $this->path ] );
194
195            return $fsFile ? $fsFile->getPath() : false;
196        }
197        // may return false
198        return $this->path;
199    }
200
201    /**
202     * Stream the file if there were no errors
203     *
204     * @param array $headers Additional HTTP headers to send on success
205     * @return Status
206     * @since 1.27
207     */
208    public function streamFileWithStatus( $headers = [] ) {
209        if ( !$this->path ) {
210            return Status::newFatal( 'backend-fail-stream', '<no path>' );
211        }
212
213        $repo = $this->file->getRepo();
214
215        if ( $repo && FileBackend::isStoragePath( $this->path ) ) {
216            return Status::wrap(
217                $repo->getBackend()->streamFile(
218                    [ 'src' => $this->path, 'headers' => $headers, ]
219                )
220            );
221        }
222
223        $streamer = new HTTPFileStreamer(
224            $this->getLocalCopyPath(),
225            $repo ? $repo->getBackend()->getStreamerOptions() : []
226        );
227
228        $success = $streamer->stream( $headers );
229
230        return $success ? Status::newGood()
231            : Status::newFatal( 'backend-fail-stream', $this->path );
232    }
233
234    /**
235     * Stream the file if there were no errors
236     *
237     * @deprecated since 1.26, use streamFileWithStatus
238     * @param array $headers Additional HTTP headers to send on success
239     * @return bool Success
240     */
241    public function streamFile( $headers = [] ) {
242        return $this->streamFileWithStatus( $headers )->isOK();
243    }
244
245    /**
246     * Wrap some XHTML text in an anchor tag with the given attributes
247     * or, fallback to a span in the absence thereof.
248     *
249     * @param array $linkAttribs
250     * @param string $contents
251     * @return string
252     */
253    protected function linkWrap( $linkAttribs, $contents ) {
254        if ( isset( $linkAttribs['href'] ) ) {
255            return Html::rawElement( 'a', $linkAttribs, $contents );
256        }
257        return Html::rawElement( 'span', $linkAttribs ?: [], $contents );
258    }
259
260    /**
261     * @param string|null $title
262     * @param string|array $params Query parameters to add
263     * @return array
264     */
265    public function getDescLinkAttribs( $title = null, $params = [] ) {
266        if ( is_array( $params ) ) {
267            $query = $params;
268        } else {
269            $query = [];
270        }
271        if ( $this->page && $this->page !== 1 ) {
272            $query['page'] = $this->page;
273        }
274        if ( $this->lang ) {
275            $query['lang'] = $this->lang;
276        }
277
278        if ( is_string( $params ) && $params !== '' ) {
279            $query = $params . '&' . wfArrayToCgi( $query );
280        }
281
282        $attribs = [
283            'href' => $this->file->getTitle()->getLocalURL( $query ),
284            'class' => 'mw-file-description',
285        ];
286
287        if ( $title ) {
288            $attribs['title'] = $title;
289        }
290
291        return $attribs;
292    }
293}
294
295/** @deprecated class alias since 1.46 */
296class_alias( MediaTransformOutput::class, 'MediaTransformOutput' );