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