Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.88% covered (warning)
84.88%
73 / 86
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
MediaFileTrait
85.88% covered (warning)
85.88%
73 / 85
0.00% covered (danger)
0.00%
0 / 4
26.76
0.00% covered (danger)
0.00%
0 / 1
 getFileInfo
89.13% covered (warning)
89.13%
41 / 46
0.00% covered (danger)
0.00%
0 / 1
10.13
 getTransformInfo
94.44% covered (success)
94.44%
17 / 18
0.00% covered (danger)
0.00%
0 / 1
7.01
 getImageLimitsFromOption
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
3.10
 getNormalizedThumbLimits
66.67% covered (warning)
66.67%
8 / 12
0.00% covered (danger)
0.00%
0 / 1
5.93
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\FileRepo\File;
8
9use MediaWiki\MainConfigNames;
10use MediaWiki\MediaWikiServices;
11use MediaWiki\Permissions\Authority;
12use MediaWiki\User\UserIdentity;
13use Wikimedia\Timestamp\TimestampFormat as TS;
14
15/**
16 * Trait for functionality related to media files
17 *
18 * @internal
19 * @ingroup FileRepo
20 */
21trait MediaFileTrait {
22    /**
23     * @param File $file
24     * @param Authority $performer for permissions check
25     * @param array $transforms array of transforms to include in the response
26     * @return array response data
27     */
28    private function getFileInfo( $file, Authority $performer, $transforms ) {
29        $urlUtils = MediaWikiServices::getInstance()->getUrlUtils();
30        // If there is a problem with the file, there is very little info we can reliably
31        // return (T228286, T239213), but we do what we can (T201205).
32        $responseFile = [
33            'title' => $file->getTitle()->getText(),
34            'file_description_url' => $urlUtils->expand( $file->getDescriptionUrl(), PROTO_RELATIVE ),
35            'latest' => null,
36            'preferred' => null,
37            'original' => null,
38        ];
39
40        foreach ( $transforms as $transformType => $_ ) {
41            $responseFile[$transformType] = null;
42        }
43
44        if ( $file->exists() ) {
45            $uploader = $file->getUploader( File::FOR_THIS_USER, $performer );
46            if ( $uploader ) {
47                $fileUser = [
48                    'id' => $uploader->getId(),
49                    'name' => $uploader->getName(),
50                ];
51            } else {
52                $fileUser = [
53                    'id' => null,
54                    'name' => null,
55                ];
56            }
57            $responseFile['latest'] = [
58                'timestamp' => wfTimestamp( TS::ISO_8601, $file->getTimestamp() ),
59                'user' => $fileUser,
60            ];
61
62            // If the file doesn't and shouldn't have a duration, return null instead of 0.
63            // Testing for 0 first, then checking mediatype, makes gifs behave as desired for
64            // both still and animated cases.
65            $duration = $file->getLength();
66            $mediaTypesWithDurations = [ MEDIATYPE_AUDIO, MEDIATYPE_VIDEO, MEDIATYPE_MULTIMEDIA ];
67            if ( $duration == 0 && !in_array( $file->getMediaType(), $mediaTypesWithDurations ) ) {
68                $duration = null;
69            }
70
71            if ( $file->allowInlineDisplay() ) {
72                foreach ( $transforms as $transformType => $transform ) {
73                    $responseFile[$transformType] = $this->getTransformInfo(
74                        $file,
75                        // @phan-suppress-next-line PhanTypeMismatchArgumentNullable False positive
76                        $duration,
77                        $transform['maxWidth'],
78                        $transform['maxHeight']
79                    );
80                }
81            }
82
83            $responseFile['original'] = [
84                'mediatype' => $file->getMediaType(),
85                'size' => $file->getSize(),
86                'width' => $file->getWidth() ?: null,
87                'height' => $file->getHeight() ?: null,
88                'duration' => $duration,
89                'url' => $urlUtils->expand( $file->getUrl(), PROTO_RELATIVE ),
90            ];
91        }
92
93        return $responseFile;
94    }
95
96    /**
97     * @param File $file
98     * @param int|null $duration File duration (if any)
99     * @param int $maxWidth Max width to display at
100     * @param int $maxHeight Max height to display at
101     * @return array|null Transform info ready to include in response, or null if unavailable
102     */
103    private function getTransformInfo( $file, $duration, $maxWidth, $maxHeight ) {
104        $transformInfo = null;
105
106        [ $width, $height ] = $file->getDisplayWidthHeight( $maxWidth, $maxHeight );
107        $transform = $file->transform( [ 'width' => $width, 'height' => $height ] );
108        if ( $transform && !$transform->isError() ) {
109            // $file->getSize() returns original size. Only include if dimensions match.
110            $size = null;
111            if ( $file->getWidth() == $transform->getWidth() &&
112                $file->getHeight() == $transform->getHeight()
113            ) {
114                $size = $file->getSize();
115            }
116
117            $transformInfo = [
118                'mediatype' => $transform->getFile()->getMediaType(),
119                'size' => $size,
120                'width' => $transform->getWidth() ?: null,
121                'height' => $transform->getHeight() ?: null,
122                'duration' => $duration,
123                'url' => MediaWikiServices::getInstance()->getUrlUtils()
124                    ->expand( $transform->getUrl(), PROTO_RELATIVE ),
125            ];
126        }
127
128        return $transformInfo;
129    }
130
131    /**
132     * Returns the corresponding $wgImageLimits entry for the selected user option.
133     *
134     * This method uses the config and user option for the visual rendered size of
135     * images on the screen, for display purposes.
136     * For thumbnail physical sizing, use MediaFileTrait::getNormalizedThumbLimits().
137     *
138     * @param UserIdentity $user
139     * @param string $optionName Name of a option to check, typically imagesize or thumbsize
140     * @return int[]
141     * @since 1.35
142     */
143    public static function getImageLimitsFromOption( UserIdentity $user, string $optionName ) {
144        $imageLimits = MediaWikiServices::getInstance()->getMainConfig()
145            ->get( MainConfigNames::ImageLimits );
146        $optionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
147        $option = $optionsLookup->getIntOption( $user, $optionName );
148        if ( !isset( $imageLimits[$option] ) ) {
149            $option = $optionsLookup->getDefaultOption( $optionName, $user );
150        }
151
152        // The user offset might still be incorrect, specially if
153        // $wgImageLimits got changed (see T10858).
154        if ( !isset( $imageLimits[$option] ) ) {
155            // Default to the first offset in $wgImageLimits
156            $option = 0;
157        }
158
159        // if nothing is set, fallback to a hardcoded default
160        return $imageLimits[$option] ?? [ 800, 600 ];
161    }
162
163    /**
164     * Returns the corresponding thumbnail width for a given width,
165     * based on the ThumbnailSteps config.
166     *
167     * This method should be used when calculating the physical
168     * dimensions for thumbnails, to ensure that we use the same dimensions
169     * as the thumbnail generator.
170     * For display purposes, use MediaFileTrait::getImageLimitsFromOption().
171     *
172     * @param int $width Requested width
173     * @return int[] Normalized width and height for the thumbnail
174     * @since 1.47
175     */
176    public static function getNormalizedThumbLimits( $width ) {
177        $thumbSteps = MediaWikiServices::getInstance()->getMainConfig()
178            ->get( MainConfigNames::ThumbnailSteps );
179        if ( !is_array( $thumbSteps ) ) {
180            // If ThumbnailSteps is not set,
181            // fall back on the requested width
182            $thumbSteps = [ $width ];
183        }
184        sort( $thumbSteps, SORT_NUMERIC );
185
186        // Find the smallest thumbnail step that is at least as large
187        // as the requested width
188        foreach ( $thumbSteps as $thumbWidth ) {
189            if ( $thumbWidth >= $width ) {
190                return [ $thumbWidth, $thumbWidth ];
191            }
192        }
193
194        // if none was found to be at least as large,
195        // return the largest thumbnail step
196        $normalizedWidth = end( $thumbSteps );
197
198        if ( $normalizedWidth <= 0 ) {
199            // Sanity check: if the config is not set properly
200            // just return the original width.
201            return [ $width, $width ];
202        }
203
204        return [ $normalizedWidth, $normalizedWidth ];
205    }
206}
207
208/** @deprecated class alias since 1.44 */
209class_alias( MediaFileTrait::class, 'MediaFileTrait' );