UFilesUpload::upload()   B
last analyzed

Complexity

Conditions 10
Paths 25

Size

Total Lines 27
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 3
Metric Value
eloc 21
c 4
b 0
f 3
dl 0
loc 27
rs 7.6666
cc 10
nc 25
nop 3

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
4
namespace Ubiquity\utils\http;
5
6
use Ubiquity\exceptions\FileUploadException;
7
use Ubiquity\utils\base\UFileSystem;
8
9
/**
10
 * File Uploader class utility.
11
 * Ubiquity\utils\http$UFilesUpload
12
 * This class is part of Ubiquity
13
 *
14
 * @author jcheron <[email protected]>
15
 * @version 0.0.0
16
 *
17
 */
18
19
class UFilesUpload {
20
	public const SUCCESS_MESSAGE='uploadSuccess';
21
	public const NO_FILE_SENT_ERR='uploadNoFileSentErr';
22
	public const FILE_SIZE_ERR='uploadFileSizeErr';
23
	public const UNKNOWN_ERR='uploadUnknownErr';
24
	public const MIME_TYPE_ERR='uploadMimeTypeErr';
25
	public const EXISTING_FILE_ERR='uploadExistingFileErr';
26
	public const UPLOAD_ERR='uploadErr';
27
	public const INVALID_FORMAT_ERR='uploadInvalidFormatErr';
28
	
29
	private const IMAGES_MIME_TYPES=['bmp'=>'image/bmp','gif'=>'image/gif','ico'=>'image/vnd.microsoft.icon','jpg'=>'image/jpeg','jpeg'=>'image/jpeg','svg'=>'image/svg+xml','png'=>'image/png','tif'=>'image/tiff','tiff'=>'image/tiff'];
30
	
31
	private int $maxFileSize=100000;
32
	private ?array $allowedMimeTypes=['pdf'=>'application/pdf'];
33
	private array $messages;
34
	private string $destDir;
35
	private array $messageTypes=[
36
			self::SUCCESS_MESSAGE=>'The file %s has been uploaded.',
37
			self::NO_FILE_SENT_ERR=>'No file sent.',
38
			self::FILE_SIZE_ERR=>'Exceeded file size limit for %s.',
39
			self::UNKNOWN_ERR=>'Unknown Error.',
40
			self::MIME_TYPE_ERR=>'The mime-type %s is not allowed for %s!',
41
			self::EXISTING_FILE_ERR=>'Sorry, The file %s already exists.',
42
			self::UPLOAD_ERR=>'Sorry, there was an error uploading the file %s.',
43
			self::INVALID_FORMAT_ERR=>'Invalid error format!'
44
	];
45
	
46
	public function __construct($destDir='upload') {
47
		$this->destDir=$destDir;
48
	}
49
	
50
	/**
51
	 * @param array|null $allowedMimeTypes
52
	 * @param int $maxFileSize
53
	 */
54
	public function setUploadOptions(?array $allowedMimeTypes=null,int $maxFileSize=100000): void {
55
		$this->allowedMimeTypes=$allowedMimeTypes;
56
		$this->maxFileSize=$maxFileSize;
57
	}
58
	
59
	/**
60
	 * @return int
61
	 */
62
	public function getMaxFileSize(): int {
63
		return $this->maxFileSize;
64
	}
65
	
66
	/**
67
	 * @param int $maxFileSize
68
	 */
69
	public function setMaxFileSize(int $maxFileSize): void {
70
		$this->maxFileSize = $maxFileSize;
71
	}
72
	
73
	/**
74
	 * @return array|string[]|null
75
	 */
76
	public function getAllowedMimeTypes(): ?array {
77
		return $this->allowedMimeTypes;
78
	}
79
	
80
	/**
81
	 * @param array|string[]|null $allowedMimeTypes
82
	 */
83
	public function setAllowedMimeTypes(?array $allowedMimeTypes): void {
84
		$this->allowedMimeTypes = $allowedMimeTypes;
85
	}
86
	
87
	public function allowImages(bool $only=true): void {
88
		$this->allowType($only,self::IMAGES_MIME_TYPES);
89
	}
90
	
91
	public function allowAllMimeTypes(): void{
92
		$this->allowedMimeTypes=null;
93
	}
94
	
95
	private function allowType(bool $only,array $typeArray): void {
96
		if($only){
97
			$this->allowedMimeTypes=$typeArray;
98
		}else{
99
			$this->allowedMimeTypes+=$typeArray;
100
		}
101
	}
102
	
103
	private function checkErrors(array $file):void{
104
		if (!isset($file['error']) || \is_array($file['error'])){
105
			throw new FileUploadException($this->getMessageType(self::INVALID_FORMAT_ERR));
106
		}
107
		switch ($file['error']) {
108
			case \UPLOAD_ERR_OK:
109
				break;
110
			case \UPLOAD_ERR_NO_FILE:
111
				throw new FileUploadException($this->getMessageType(self::NO_FILE_SENT_ERR));
112
			case \UPLOAD_ERR_INI_SIZE:
113
			case \UPLOAD_ERR_FORM_SIZE:
114
				throw new FileUploadException($this->getMessageType(self::FILE_SIZE_ERR,$this->getDisplayedFileName($file)));
115
			default:
116
				throw new FileUploadException($this->getMessageType(self::UNKNOWN_ERR,$this->getDisplayedFileName($file)));
117
		}
118
	}
119
	
120
	private function getMessageType(string $type,string ...$params){
121
		return \vsprintf($this->messageTypes[$type]??('No message for '.$type), $params);
122
	}
123
	
124
	private function checkTypeMime(array $file): bool {
125
		$allowedMimeTypes=$this->getAllowedMimeTypes();
126
		if(\is_array($allowedMimeTypes)) {
127
			$finfo = new \finfo(\FILEINFO_MIME_TYPE);
128
			if(\array_search($finfo->file($file['tmp_name']), $this->getAllowedMimeTypes(), true) === false){
129
				$this->messages['errors'][]=$this->getMessageType(self::MIME_TYPE_ERR, $finfo->file($file['tmp_name']),$this->getDisplayedFileName($file));
130
				return false;
131
			}
132
		}
133
		return true;
134
	}
135
	
136
	private function checkFileSize(array $file): bool{
137
		if ($file['size'] > $this->maxFileSize) {
138
			$this->messages['errors'][]=$this->getMessageType(self::FILE_SIZE_ERR,$this->getDisplayedFileName($file));
139
			return false;
140
		}
141
		return true;
142
	}
143
	
144
	private function getDisplayedFileName(array $file): string {
145
		return \htmlspecialchars(\basename($file['name']));
146
	}
147
	
148
	/**
149
	 * Redefine the messages displayed.
150
	 * @param array $messages
151
	 */
152
	public function setMessageTypes(array $messages): void {
153
		$this->messageTypes=\array_merge($this->messageTypes,$messages);
154
	}
155
	
156
	/**
157
	 * Get the message types displayed.
158
	 * @return array
159
	 */
160
	public function getMessageTypes(): array {
161
		return $this->messageTypes;
162
	}
163
164
	/**
165
	 * Uploads files to $destDir directory.
166
	 * @param string|null $destDir is relative to ROOT app
167
	 * @param bool $force if True, replace existing files
168
	 * @param callable|null $fileNameCallback returns an updated version of the dest filename i.e. function($filename){ return '_'.$filename;}
169
	 */
170
	public function upload(string $destDir=null,bool $force=true,?callable $filenameCallback=null): void {
171
		$destDir??=$this->destDir;
172
		$this->messages=[];
173
		$dest=\ROOT.\DS.$destDir.\DS;
174
		UFileSystem::safeMkdir(\ROOT.\DS.$destDir.\DS);
175
		try{
176
			foreach($_FILES as $file){
177
				$this->checkErrors($file);
178
				if($this->checkTypeMime($file) && $this->checkFileSize($file)) {
179
					$filename = \basename($file['name']);
180
					if(isset($filenameCallback) && \is_callable($filenameCallback)){
181
						$filename=$filenameCallback($filename)??$filename;
182
					}
183
					$dFileName=\htmlspecialchars($filename);
184
					if ($force || !\file_exists($dest . $filename)) {
185
						if (\move_uploaded_file($file['tmp_name'], $dest . $filename)) {
186
							$this->messages['success'][] = $this->getMessageType(self::SUCCESS_MESSAGE, $dFileName);
187
						} else {
188
							$this->messages['errors'][] = $this->getMessageType(self::UPLOAD_ERR, $dFileName);
189
						}
190
					} else {
191
						$this->messages['errors'][] = $this->getMessageType(self::EXISTING_FILE_ERR, $dFileName);
192
					}
193
				}
194
			}
195
		}catch(\Exception $e){
196
			$this->messages['errors'][]=$e->getMessage();
197
		}
198
	}
199
	
200
	/**
201
	 * Returns true if the upload generated at least one error.
202
	 * @return bool
203
	 */
204
	public function hasErrors(): bool {
205
		return isset($this->messages['errors']) && \count($this->messages['errors'])>0;
206
	}
207
	
208
	/**
209
	 * Returns true if at least one file has been uploaded.
210
	 * @return bool
211
	 */
212
	public function isSuccess(): bool {
213
		return isset($this->messages['success']) && \count($this->messages['success'])>0;
214
	}
215
	
216
	/**
217
	 * Returns all success messages.
218
	 * @return array|mixed
219
	 */
220
	public function getMessages(): array {
221
		return $this->messages['success']??[];
222
	}
223
	
224
	/**
225
	 * Returns all error messages.
226
	 * @return array|mixed
227
	 */
228
	public function getErrorMessages(): array {
229
		return $this->messages['errors']??[];
230
	}
231
}
232