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