Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
77.08% covered (warning)
77.08%
37 / 48
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
24use Wikimedia\AtEase\AtEase;
25use Wikimedia\Timestamp\ConvertibleTimestamp;
26
27/**
28 * Class representing a non-directory file on the file system
29 *
30 * @ingroup FileBackend
31 */
32class FSFile {
33    /** @var string Path to file */
34    protected $path;
35
36    /** @var string File SHA-1 in base 36 */
37    protected $sha1Base36;
38
39    /**
40     * Sets up the file object
41     *
42     * @param string $path Path to temporary file on local disk
43     */
44    public function __construct( $path ) {
45        $this->path = $path;
46    }
47
48    /**
49     * Returns the file system path
50     *
51     * @return string
52     */
53    public function getPath() {
54        return $this->path;
55    }
56
57    /**
58     * Checks if the file exists
59     *
60     * @return bool
61     */
62    public function exists() {
63        return is_file( $this->path );
64    }
65
66    /**
67     * Get the file size in bytes
68     *
69     * @return int|bool
70     */
71    public function getSize() {
72        AtEase::suppressWarnings();
73        $size = filesize( $this->path );
74        AtEase::restoreWarnings();
75
76        return $size;
77    }
78
79    /**
80     * Get the file's last-modified timestamp
81     *
82     * @return string|bool TS_MW timestamp or false on failure
83     */
84    public function getTimestamp() {
85        AtEase::suppressWarnings();
86        $timestamp = filemtime( $this->path );
87        AtEase::restoreWarnings();
88        if ( $timestamp !== false ) {
89            $timestamp = ConvertibleTimestamp::convert( TS_MW, $timestamp );
90        }
91
92        return $timestamp;
93    }
94
95    /**
96     * Get an associative array containing information about
97     * a file with the given storage path.
98     *
99     * Resulting array fields include:
100     *   - fileExists
101     *   - size (filesize in bytes)
102     *   - mime (as major/minor)
103     *   - file-mime (as major/minor)
104     *   - sha1 (in base 36)
105     *   - major_mime
106     *   - minor_mime
107     *
108     * @param string|bool $ext The file extension, or true to extract it from the filename.
109     *             Set it to false to ignore the extension. Currently unused.
110     * @return array
111     */
112    public function getProps( $ext = true ) {
113        $info = self::placeholderProps();
114        $info['fileExists'] = $this->exists();
115
116        if ( $info['fileExists'] ) {
117            $info['size'] = $this->getSize(); // bytes
118            $info['sha1'] = $this->getSha1Base36();
119
120            $mime = mime_content_type( $this->path );
121            # MIME type according to file contents
122            $info['file-mime'] = ( $mime === false ) ? 'unknown/unknown' : $mime;
123            # logical MIME type
124            $info['mime'] = $mime;
125
126            if ( strpos( $mime, '/' ) !== false ) {
127                [ $info['major_mime'], $info['minor_mime'] ] = explode( '/', $mime, 2 );
128            } else {
129                [ $info['major_mime'], $info['minor_mime'] ] = [ $mime, 'unknown' ];
130            }
131        }
132
133        return $info;
134    }
135
136    /**
137     * Placeholder file properties to use for files that don't exist
138     *
139     * Resulting array fields include:
140     *   - fileExists
141     *   - size (filesize in bytes)
142     *   - mime (as major/minor)
143     *   - file-mime (as major/minor)
144     *   - sha1 (in base 36)
145     *   - major_mime
146     *   - minor_mime
147     *
148     * @return array
149     */
150    public static function placeholderProps() {
151        $info = [];
152        $info['fileExists'] = false;
153        $info['size'] = 0;
154        $info['file-mime'] = null;
155        $info['major_mime'] = null;
156        $info['minor_mime'] = null;
157        $info['mime'] = null;
158        $info['sha1'] = '';
159
160        return $info;
161    }
162
163    /**
164     * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
165     * encoding, zero padded to 31 digits.
166     *
167     * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
168     * fairly neatly.
169     *
170     * @param bool $recache
171     * @return bool|string False on failure
172     */
173    public function getSha1Base36( $recache = false ) {
174        if ( $this->sha1Base36 !== null && !$recache ) {
175            return $this->sha1Base36;
176        }
177
178        AtEase::suppressWarnings();
179        $this->sha1Base36 = sha1_file( $this->path );
180        AtEase::restoreWarnings();
181
182        if ( $this->sha1Base36 !== false ) {
183            $this->sha1Base36 = Wikimedia\base_convert( $this->sha1Base36, 16, 36, 31 );
184        }
185
186        return $this->sha1Base36;
187    }
188
189    /**
190     * Get the final file extension from a file system path
191     *
192     * @param string $path
193     * @return string
194     */
195    public static function extensionFromPath( $path ) {
196        $i = strrpos( $path, '.' );
197
198        return strtolower( $i ? substr( $path, $i + 1 ) : '' );
199    }
200
201    /**
202     * Get an associative array containing information about a file in the local filesystem.
203     *
204     * @param string $path Absolute local filesystem path
205     * @param string|bool $ext The file extension, or true to extract it from the filename.
206     *   Set it to false to ignore the extension.
207     * @return array
208     */
209    public static function getPropsFromPath( $path, $ext = true ) {
210        $fsFile = new self( $path );
211
212        return $fsFile->getProps( $ext );
213    }
214
215    /**
216     * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
217     * encoding, zero padded to 31 digits.
218     *
219     * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
220     * fairly neatly.
221     *
222     * @param string $path
223     * @return false|string False on failure
224     */
225    public static function getSha1Base36FromPath( $path ) {
226        $fsFile = new self( $path );
227
228        return $fsFile->getSha1Base36();
229    }
230}