Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.51% covered (warning)
75.51%
37 / 49
63.64% covered (warning)
63.64%
7 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
FSFile
77.08% covered (warning)
77.08%
37 / 48
63.64% covered (warning)
63.64%
7 / 11
23.34
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 exists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSize
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getTimestamp
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getProps
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
4.01
 placeholderProps
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 getSha1Base36
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 extensionFromPath
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getPropsFromPath
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getSha1Base36FromPath
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Non-directory file on the file system.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup FileBackend
22 */
23
24namespace Wikimedia\FileBackend\FSFile;
25
26use Wikimedia\AtEase\AtEase;
27use Wikimedia\Timestamp\ConvertibleTimestamp;
28
29/**
30 * Class representing a non-directory file on the file system
31 *
32 * @ingroup FileBackend
33 */
34class FSFile {
35    /** @var string Path to file */
36    protected $path;
37
38    /** @var string File SHA-1 in base 36 */
39    protected $sha1Base36;
40
41    /**
42     * Sets up the file object
43     *
44     * @param string $path Path to temporary file on local disk
45     */
46    public function __construct( $path ) {
47        $this->path = $path;
48    }
49
50    /**
51     * Returns the file system path
52     *
53     * @return string
54     */
55    public function getPath() {
56        return $this->path;
57    }
58
59    /**
60     * Checks if the file exists
61     *
62     * @return bool
63     */
64    public function exists() {
65        return is_file( $this->path );
66    }
67
68    /**
69     * Get the file size in bytes
70     *
71     * @return int|bool
72     */
73    public function getSize() {
74        AtEase::suppressWarnings();
75        $size = filesize( $this->path );
76        AtEase::restoreWarnings();
77
78        return $size;
79    }
80
81    /**
82     * Get the file's last-modified timestamp
83     *
84     * @return string|bool TS_MW timestamp or false on failure
85     */
86    public function getTimestamp() {
87        AtEase::suppressWarnings();
88        $timestamp = filemtime( $this->path );
89        AtEase::restoreWarnings();
90        if ( $timestamp !== false ) {
91            $timestamp = ConvertibleTimestamp::convert( TS_MW, $timestamp );
92        }
93
94        return $timestamp;
95    }
96
97    /**
98     * Get an associative array containing information about
99     * a file with the given storage path.
100     *
101     * Resulting array fields include:
102     *   - fileExists
103     *   - size (filesize in bytes)
104     *   - mime (as major/minor)
105     *   - file-mime (as major/minor)
106     *   - sha1 (in base 36)
107     *   - major_mime
108     *   - minor_mime
109     *
110     * @param string|bool $ext The file extension, or true to extract it from the filename.
111     *             Set it to false to ignore the extension. Currently unused.
112     * @return array
113     */
114    public function getProps( $ext = true ) {
115        $info = self::placeholderProps();
116        $info['fileExists'] = $this->exists();
117
118        if ( $info['fileExists'] ) {
119            $info['size'] = $this->getSize(); // bytes
120            $info['sha1'] = $this->getSha1Base36();
121
122            $mime = mime_content_type( $this->path );
123            # MIME type according to file contents
124            $info['file-mime'] = ( $mime === false ) ? 'unknown/unknown' : $mime;
125            # logical MIME type
126            $info['mime'] = $mime;
127
128            if ( strpos( $mime, '/' ) !== false ) {
129                [ $info['major_mime'], $info['minor_mime'] ] = explode( '/', $mime, 2 );
130            } else {
131                [ $info['major_mime'], $info['minor_mime'] ] = [ $mime, 'unknown' ];
132            }
133        }
134
135        return $info;
136    }
137
138    /**
139     * Placeholder file properties to use for files that don't exist
140     *
141     * Resulting array fields include:
142     *   - fileExists
143     *   - size (filesize in bytes)
144     *   - mime (as major/minor)
145     *   - file-mime (as major/minor)
146     *   - sha1 (in base 36)
147     *   - major_mime
148     *   - minor_mime
149     *
150     * @return array
151     */
152    public static function placeholderProps() {
153        $info = [];
154        $info['fileExists'] = false;
155        $info['size'] = 0;
156        $info['file-mime'] = null;
157        $info['major_mime'] = null;
158        $info['minor_mime'] = null;
159        $info['mime'] = null;
160        $info['sha1'] = '';
161
162        return $info;
163    }
164
165    /**
166     * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
167     * encoding, zero padded to 31 digits.
168     *
169     * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
170     * fairly neatly.
171     *
172     * @param bool $recache
173     * @return bool|string False on failure
174     */
175    public function getSha1Base36( $recache = false ) {
176        if ( $this->sha1Base36 !== null && !$recache ) {
177            return $this->sha1Base36;
178        }
179
180        AtEase::suppressWarnings();
181        $this->sha1Base36 = sha1_file( $this->path );
182        AtEase::restoreWarnings();
183
184        if ( $this->sha1Base36 !== false ) {
185            $this->sha1Base36 = \Wikimedia\base_convert( $this->sha1Base36, 16, 36, 31 );
186        }
187
188        return $this->sha1Base36;
189    }
190
191    /**
192     * Get the final file extension from a file system path
193     *
194     * @param string $path
195     * @return string
196     */
197    public static function extensionFromPath( $path ) {
198        $i = strrpos( $path, '.' );
199
200        return strtolower( $i ? substr( $path, $i + 1 ) : '' );
201    }
202
203    /**
204     * Get an associative array containing information about a file in the local filesystem.
205     *
206     * @param string $path Absolute local filesystem path
207     * @param string|bool $ext The file extension, or true to extract it from the filename.
208     *   Set it to false to ignore the extension.
209     * @return array
210     */
211    public static function getPropsFromPath( $path, $ext = true ) {
212        $fsFile = new self( $path );
213
214        return $fsFile->getProps( $ext );
215    }
216
217    /**
218     * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
219     * encoding, zero padded to 31 digits.
220     *
221     * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
222     * fairly neatly.
223     *
224     * @param string $path
225     * @return false|string False on failure
226     */
227    public static function getSha1Base36FromPath( $path ) {
228        $fsFile = new self( $path );
229
230        return $fsFile->getSha1Base36();
231    }
232}
233
234/** @deprecated class alias since 1.43 */
235class_alias( FSFile::class, 'FSFile' );