GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Uploader   B
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 375
Duplicated Lines 4.27 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 42
c 0
b 0
f 0
lcom 1
cbo 2
dl 16
loc 375
rs 8.295

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 1 1
A uploadPicture() 0 4 1
A uploadFile() 0 3 1
A uploadCSV() 0 3 1
C upload() 0 82 8
A getAllowedMime() 0 3 2
A deleteFiles() 0 11 3
A deleteFile() 0 9 3
A createDirectory() 0 16 4
A deleteDir() 0 5 2
A isFileExists() 0 3 2
B delTree() 0 10 5
A mime() 16 16 3
A getHashedName() 0 5 2
A MimeToExtension() 0 13 2
A getFileName() 0 7 1
A errors() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Uploader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Uploader, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
 /**
4
  * Uploader Class
5
  *
6
  * Main class for uploading, deleting files & directories
7
  *
8
  * @license    http://opensource.org/licenses/MIT The MIT License (MIT)
9
  * @author     Omar El Gabry <[email protected]>
10
  */
11
12
class Uploader{
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
13
14
    /**
15
     * The mime types allowed for upload
16
     *
17
     * @var array
18
     */
19
    private static $allowedMIME = [
20
        "image" => array('image/jpeg', 'image/png', 'image/gif'),
21
        "csv"   => array('text/csv', 'application/vnd.ms-excel', 'text/plain'),
22
        "file"  => array('application/msword',
23
                         'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
24
                         'application/pdf',
25
                         'application/zip',
26
                         'application/vnd.ms-powerpoint')
27
    ];
28
29
    /**
30
     * The min and max image size allowed for upload (in bytes)
31
     * 1 KB = 1024 bytes, and 1 MB = 1048,576 bytes.
32
     *
33
     * @var array
34
     */
35
    private static $fileSize = [100, 5242880];
36
37
    /**
38
     * The max height and width image allowed for image
39
     *
40
     * @var array
41
     */
42
    private static $dimensions = [2000, 2000];
43
44
    /**
45
     * Array of validation errors
46
     *
47
     * @var array
48
     */
49
    private static $errors = [];
50
51
    /***
52
     * @access private
53
     */
54
    private function __construct() {}
55
56
    /**
57
     * upload profile picture
58
     *
59
     * @param  array   $file
60
     * @param  mixed   $id random id used in creating filename
61
     * @return mixed   false in case of failure, otherwise array of file created
62
     *
63
     */
64
    public static function uploadPicture($file, $id){
65
        self::$fileSize = [100, 2097152];
66
        return self::upload($file, IMAGES . "profile_pictures/", $id, "image");
67
    }
68
69
    /**
70
     * upload a file - default
71
     *
72
     * @param  array    $file
73
     * @param  mixed    $id random id used for creating filename
74
     * @return mixed    false in case of failure, array otherwise
75
     *
76
     */
77
    public static function uploadFile($file, $id = null){
78
        return self::upload($file, APP . "uploads/" , $id);
79
    }
80
81
    /**
82
     * upload a CSV File
83
     *
84
     * @param  array    $file
85
     * @return mixed    false in case of failure, array otherwise
86
     *
87
     */
88
    public static function uploadCSV($file){
89
        return self::upload($file, APP . "uploads/", null, "csv");
90
    }
91
92
    /**
93
     * upload & validate file
94
     *
95
     * @param  array    $file
96
     * @param  string   $dir directory where we will upload the file
97
     * @param  mixed    $id random id used for creating filename
98
     * @param  string   $type it tells whether the file is image, csv, or normal file(default).
99
     * @return mixed    false in case of failure, array otherwise
100
     * @throws Exception If file couldn't be uploaded
101
     *
102
     */
103
    private static function upload($file, $dir, $id, $type = "file"){
104
105
        $mimeTypes  = self::getAllowedMime($type);
106
107
        $validation = new Validation();
108
        $rules = "required|fileErrors|fileUploaded|mimeType(".Utility::commas($mimeTypes).")|fileSize(".Utility::commas(self::$fileSize).")";
109
        $rules = ($type === "image")? $rules . "|imageSize(".Utility::commas(self::$dimensions).")": $rules;
110
111
        if(!$validation->validate([
112
            "File" => [$file, $rules]], true)){
113
            self::$errors = $validation->errors();
114
            return false;
115
        }
116
117
        if($type === "csv"){
118
119
            // you need to add the extension in case of csv files,
120
            // because mime() will return text/plain.
121
            $basename = "grades" . "." . "csv";
122
            $path = $dir . $basename;
123
124
            $data = ["basename" => $basename, "extension" => "csv"];
125
126
        } else {
127
128
            if(!empty($id)){
129
130
                // get safe filename
131
                $filename = self::getFileName($file);
132
133
                // mime mapping to extension
134
                $ext = self::MimeToExtension(self::mime($file));
135
136
                // get hashed version using the given $id
137
                // the $id is used to have a unique file name
138
                // so, for example you would use it for profile picture,
139
                // because every user can have only one picture
140
                $hashedFileName = self::getHashedName($id);
141
142
                $basename = $hashedFileName . "." . $ext;
143
                $path = $dir . $basename;
144
145
                // delete all files with the same name, but with different formats.
146
                // not needed, but i like to clear unnecessary files
147
                self::deleteFiles($dir . $hashedFileName, $mimeTypes);
148
149
                $data = ["filename" => $filename, "basename" => $basename, "hashed_filename" => $hashedFileName, "extension" => $ext];
150
151
            } else {
152
153
                $filename = self::getFileName($file);
154
                $ext = self::MimeToExtension(self::mime($file));
155
156
                // hashed file name is created from the original filename and extension
157
                // so uploading test.pdf & test.doc won't conflict,
158
                // but, uploading file with test.pdf will return "file already exists"
159
                $hashedFileName = self::getHashedName(strtolower($filename . $ext));
160
161
                $basename = $hashedFileName . "." . $ext;
162
                $path = $dir . $basename;
163
164
                if(!$validation->validate(["File" => [$path, "fileUnique"]])) {
165
                    self::$errors = $validation->errors();
166
                    return false;
167
                }
168
169
                $data = ["filename" => $filename, "basename" => $basename, "hashed_filename" => $hashedFileName, "extension" => $ext];
170
            }
171
        }
172
173
        // upload the file.
174
        if(!move_uploaded_file($file['tmp_name'], $path)){
175
            throw new Exception("File couldn't be uploaded");
176
        }
177
178
        // set 644 permission to avoid any executable files
179
        if(!chmod($path, 0644)) {
180
            throw new Exception("File permissions couldn't be changed");
181
        }
182
183
        return $data;
184
    }
185
186
    /**
187
     * get mime type allowed from $allowedMIME
188
     *
189
     * @param string $key
190
     * @return array
191
     */
192
    private static function getAllowedMime($key){
193
        return isset(self::$allowedMIME[$key])? self::$allowedMIME[$key]: [];
194
    }
195
196
    /**
197
     * If you can have only one file name based on each user, Then:
198
     * Before uploading every new file, Delete all files with the same name and different extensions
199
     *
200
     * @param  string   $filePathWithoutExtension
201
     * @param  array    $allowedMIME
202
     *
203
     */
204
    private static function deleteFiles($filePathWithoutExtension, $allowedMIME){
205
206
        foreach($allowedMIME as $mime){
207
            $ext = self::MimeToExtension($mime);
208
            $path = $filePathWithoutExtension . "." . $ext;
209
210
            if(file_exists($path)){
211
                unlink($path);
212
            }
213
        }
214
    }
215
216
    /**
217
     * Deletes a file
218
     *
219
     * @param  string   $path
220
     * @throws Exception    File couldn't be deleted
221
     *
222
     */
223
    public static function deleteFile($path){
224
        if(file_exists($path)){
225
            if(!unlink($path)){
226
                throw new Exception("File ". $path ." couldn't be deleted");
227
            }
228
        } else {
229
            throw new Exception("File ". $path ." doesn't exist!");
230
        }
231
    }
232
233
    /**
234
     * create a directory with random hashed name
235
     *
236
     * @param  string       $dir
237
     * @return string
238
     * @throws Exception    If directory couldn't be created or If directory already exists!
239
     */
240
    public static function createDirectory($dir){
241
242
        $hashedDirName = self::getHashedName();
243
        $newDir = $dir . $hashedDirName;
244
245
        // create a directory if not exists
246
        if(!file_exists($newDir) && !is_dir($newDir)){
247
            if(mkdir($newDir, 0755) === false){
248
                throw new Exception("directory couldn't be created");
249
            }
250
        } else {
251
            throw new Exception("Directory: " .$hashedDirName. "already exists or directory given is invalid");
252
        }
253
254
        return $hashedDirName;
255
    }
256
257
    /**
258
     * Deletes a directory.
259
     *
260
     * @param  string     $dir
261
     * @throws Exception  If directory couldn't be deleted
262
     */
263
    public static function deleteDir($dir){
264
        if(!self::delTree($dir)){
265
            throw new Exception("Directory: " . $dir ." couldn't be deleted");
266
        }
267
    }
268
269
    /**
270
     * checks if file exists in the File System or not
271
     *
272
     * @param  string   $path
273
     * @return boolean
274
     *
275
     */
276
    public static function isFileExists($path){
277
       return file_exists($path) && is_file($path);
278
    }
279
280
    /**
281
     * deletes a directory recursively
282
     *
283
     * @param  string  $dir
284
     * @return boolean
285
     *
286
     */
287
    private static function delTree($dir) {
288
289
        $files = scandir($dir);
290
        foreach ($files as $file) {
291
            if ($file != "." && $file != ".."){
292
                (is_dir("$dir/$file")) ? self::delTree("$dir/$file") : unlink("$dir/$file");
293
            }
294
        }
295
        return rmdir($dir);
296
    }
297
298
    /**
299
     * get mime type of file
300
     *
301
     * Don't use either $_FILES["file"]["type"], or pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION,
302
     * Because their values aren't secure and can be easily be spoofed.
303
     *
304
     * @param   array  $file
305
     * @return  mixed  false if failed, string otherwise
306
     * @throws Exception if finfo_open() method doesn't exists
307
     *
308
     */
309 View Code Duplication
    private static function mime($file){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
310
311
        if(!file_exists($file["tmp_name"])){
312
            return false;
313
        }
314
        if(!function_exists('finfo_open')) {
315
            throw new Exception("Function finfo_open() doesn't exist");
316
        }
317
318
        $finfo_open = finfo_open(FILEINFO_MIME_TYPE);
319
        $finfo_file = finfo_file($finfo_open, $file["tmp_name"]);
320
        finfo_close($finfo_open);
321
322
        list($mime) = explode(';', $finfo_file);
323
        return $mime;
324
    }
325
326
    /**
327
     * get hashed file name, and Optionally provided by an id
328
     *
329
     * @access private
330
     * @param   string  $id random id
331
     * @return  string  hashed file name
332
     *
333
     */
334
    private static function getHashedName($id = null){
335
336
        if($id === null) $id = time();
337
        return substr(hash('sha256', $id), 0, 40);
338
    }
339
340
    /**
341
     * Convert/Map the MIME of a file to extension
342
     *
343
     * @param   string  $mime
344
     * @return  string  extension
345
     *
346
     */
347
    private static function MimeToExtension($mime){
348
        $arr = array(
349
            'image/jpeg' => 'jpeg', // for both jpeg & jpg.
350
            'image/png' => 'png',
351
            'image/gif' => 'gif',
352
            'application/msword' => 'doc',
353
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
354
            'application/pdf' => 'pdf',
355
            'application/zip' => 'zip',
356
            'application/vnd.ms-powerpoint' => 'ppt'
357
        );
358
        return isset($arr[$mime])? $arr[$mime]: null;
359
    }
360
361
    /**
362
     * get file name
363
     * This ensures file name will be safe
364
     *
365
     * @param   array  $file
366
     * @return  string
367
     *
368
     */
369
    private static function getFileName($file){
370
371
        $filename = pathinfo($file['name'], PATHINFO_FILENAME);
372
        $filename = preg_replace("/([^A-Za-z0-9_\-\.]|[\.]{2})/", "", $filename);
373
        $filename = basename($filename);
374
        return $filename;
375
    }
376
377
    /**
378
     * get errors
379
     *
380
     * @return array errors
381
     */
382
    public static function errors(){
383
        return self::$errors;
384
    }
385
386
}
387