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