FileManager   F
last analyzed

Complexity

Total Complexity 62

Size/Duplication

Total Lines 439
Duplicated Lines 0 %

Test Coverage

Coverage 67.23%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 62
eloc 156
dl 0
loc 439
rs 3.44
c 1
b 0
f 0
ccs 119
cts 177
cp 0.6723

32 Methods

Rating   Name   Duplication   Size   Complexity  
A update() 0 7 2
A copy() 0 6 1
A getFileName() 0 4 2
A getDrive() 0 6 2
A findFile() 0 6 2
A saveFileFromUrl() 0 3 1
A saveFromUrl() 0 3 1
A findReferences() 0 26 4
A getUrl() 0 3 1
A getFileHash() 0 13 5
A setFileName() 0 9 2
A getVisibility() 0 8 2
A read() 0 9 2
A append() 0 4 1
A getMeta() 0 8 2
A readStream() 0 8 2
A getImage() 0 4 1
A exists() 0 8 2
A delete() 0 7 2
A getSize() 0 4 2
A _generatePath() 0 10 3
A save() 0 18 3
A setMeta() 0 8 2
A sendFile() 0 16 2
A _checkFileSizeLimit() 0 7 3
A destroy() 0 5 2
A prepend() 0 4 1
A getDriveFor() 0 4 1
A _get() 0 3 1
A setVisibility() 0 8 2
A saveFile() 0 7 2
A restore() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like FileManager 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.

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 FileManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @link http://www.newicon.net/neon
4
 * @copyright Copyright (c) 2016 Newicon Ltd
5
 * @license http://www.newicon.net/neon/license/
6
 * @author Steve O'Brien <[email protected]> 26/11/2016 12:24
7
 * @package neon
8
 */
9
10
namespace neon\firefly\services;
11
12
use neon\core\db\Query;
13
use neon\core\helpers\Url;
14
use neon\daedalus\services\ddsManager\models\DdsMember;
15
use neon\firefly\services\fileManager\FileNotFoundException;
16
use \neon\firefly\services\fileManager\interfaces\IFileManager;
17
use \neon\firefly\services\fileManager\models\FileManager as Model;
18
use neon\firefly\services\fileManager\FileExceedsMemoryLimitException;
19
use yii\base\InvalidConfigException;
20
use yii\web\HttpException;
21
22
/**
23
 * The file manager intends to abstract things like paths and drives from the application
24
 * instead giving the application a uuid that refers to a file.  Meanwhile this file can be moved,
25
 * and changed about without breaking.
26
 * Class FileManager
27
 * @package neon\firefly
28
 */
29
class FileManager implements IFileManager
30
{
31
32
	/**
33
	 * The default drive to use to store files
34
	 * @var  string  the Drive name
35
	 */
36
	public $defaultDrive = null;
37
38
	/**
39
	 * @inheritdoc
40
	 */
41 30
	public function save($contents, $name = null, $meta = null)
42
	{
43 30
		$driver = $this->getDrive();
44 30
		$fm = new Model();
45 30
		$path = $this->_generatePath($name, $fm);
46 30
		$success = $driver->put($path, $contents);
47 30
		if ($success) {
48 30
			$fm->attributes = [
49 30
				'name' => $name,
50 30
				'path' => $path,
51 30
				'drive' => $driver->getName(),
52 30
				'mime_type' => $driver->getMimeType($path),
53 30
				'size' => $driver->getSize($path),
54 30
				'meta' => $meta
55
			];
56 30
			$success = $fm->save();
57
		}
58 30
		return $success ? $fm->uuid : null;
59
	}
60
61
	/**
62
	 * @inheritdoc
63
	 */
64 4
	public function saveFile($file, $meta = null)
65
	{
66 4
		neon()->firefly->isFileObjectValid($file);
67 4
		$stream = fopen($file->getRealPath(), 'r+');
68 4
		$uuid = $this->save($stream, $file->getFilename(), $meta);
69 4
		if (is_resource($stream)) fclose($stream);
70 4
		return $uuid;
71
	}
72
73
	/**
74
	 * @inheritdoc
75
	 */
76
	public function saveFromUrl($url, $name, $meta = null)
77
	{
78
		return $this->save(fopen($url, 'r'), $name, $meta);
79
	}
80
81
	/**
82
	 * Save a file from a url into firefly.
83
	 * Make sure the source is trusted - if its an image then you can run through imageManager->process
84
	 * @param string $url
85
	 * @param string $name
86
	 * @return string|null
87
	 */
88
	public function saveFileFromUrl($url, $name)
89
	{
90
		return $this->save(fopen($url, 'r'), $name);
91
	}
92
93
	/**
94
	 * Look up all dds fields that reference this item
95
	 * @param $uuid
96
	 *
97
	 */
98
	public function findReferences($uuid)
99
	{
100
		// find all image and file reference datatypes
101
		// does not seem to be an interface option to lookup all members of a type across dds classes
102
		// find all members with type:
103
		$all = DdsMember::find()->where(['data_type_ref' => 'image_ref'])
104
			->orWhere(['data_type_ref' => 'file_ref'])
105
			->orWhere(['data_type_ref' => 'file_ref_multi'])
106
			->all();
107
			
108
		if (empty($all))
109
			return [];
110
111
		$q = new Query();
112
		foreach ($all as $i => $field) {
113
			if ($i === 0) {
114
				$q->select(['_uuid'],"'$field[class_type]' as 'class',")
115
					->from('ddt_' . $field['class_type'])
116
					->where([$field['member_ref'] => $uuid]);
117
			} else {
118
				$q->union((new Query())->select(['_uuid'],"'$field[class_type]' as 'class',")
119
					->from('ddt_' . $field['class_type'])
120
					->where([$field['member_ref'] => $uuid]));
121
			}
122
		}
123
		return $q->all();
124
	}
125
126
	/**
127
	 * @inheritdoc
128
	 */
129 28
	public function getMeta($uuid)
130
	{
131
		try {
132 28
			$model = $this->_get($uuid);
133 2
		} catch (FileNotFoundException $e) {
134 2
			return [];
135
		}
136 26
		return $model->toArray();
137
	}
138
139
	/**
140
	 * @inheritdoc
141
	 */
142 2
	public function exists($uuid)
143
	{
144
		try {
145 2
			$this->_get($uuid);
146 2
		} catch (FileNotFoundException $e) {
147 2
			return false;
148
		}
149 2
		return true;
150
	}
151
152
	/**
153
	 * @inheritdoc
154
	 */
155 12
	public function read($uuid)
156
	{
157
		try {
158 12
			$drive = $this->getDriveFor($uuid, $file);
159 2
		} catch (FileNotFoundException $e) {
160 2
			return false;
161
		}
162 10
		$this->_checkFileSizeLimit($uuid);
163 8
		return $drive->read($file['path']);
164
	}
165
166
	/**
167
	 * @inheritdoc
168
	 */
169 4
	public function readStream($uuid)
170
	{
171
		try {
172 4
			$drive = $this->getDriveFor($uuid, $file);
173 2
		} catch (FileNotFoundException $e) {
174 2
			return false;
175
		}
176 2
		return $drive->readStream($file['path']);
177
	}
178
179
	/**
180
	 * @inheritdoc
181
	 */
182
	public function getUrl($uuid)
183
	{
184
		return  Url::to(['/firefly/file/get', 'id' => $uuid], true);
185
	}
186
187
	/**
188
	 * @inheritdoc
189
	 */
190
	public function getImage($uuid, $params=[])
191
	{
192
		$p = array_merge(['/firefly/file/img', 'id'=>$uuid], $params);
193
		return Url::to($p, true);
194
	}
195
196
	/**
197
	 * @inheritdoc
198
	 */
199 2
	public function getVisibility($uuid)
200
	{
201
		try {
202 2
			$drive = $this->getDriveFor($uuid, $file);
203
		} catch (FileNotFoundException $e) {
204
			return false;
205
		}
206 2
		return $drive->getVisibility($file['path']);
207
	}
208
209
	/**
210
	 * @inheritdoc
211
	 */
212 2
	public function setVisibility($uuid, $visibility)
213
	{
214
		try {
215 2
			$drive = $this->getDriveFor($uuid, $file);
216
		} catch (FileNotFoundException $e) {
217
			return false;
218
		}
219 2
		return $drive->setVisibility($file['path'], $visibility);
220
	}
221
222
	/**
223
	 * @inheritdoc
224
	 */
225
	public function copy($uuid)
226
	{
227
		$model = $this->_get($uuid);
228
		$content = neon()->firefly->readStream($uuid);
229
		$newUuid = neon()->firefly->save($content, $model->name, $model->meta);
230
		return $newUuid;
231
	}
232
233
	/**
234
	 * @inheritdoc
235
	 */
236 2
	public function delete($uuid)
237
	{
238
		try {
239 2
			$f = $this->_get($uuid);
240
			return $f->delete();
241 2
		} catch(FileNotFoundException $e) {
242 2
			return false;
243
		}
244
	}
245
246
	/**
247
	 * @inheritdoc
248
	 */
249 6
	public function destroy($uuid)
250
	{
251 6
		$file = Model::findWithDeleted()->where(['uuid' => $uuid])->one();
252 6
		if ($file === null) return false;
253 6
		return $file->destroy();
254
	}
255
256
	/**
257
	 * @inheritdoc
258
	 */
259
	public function restore($uuid)
260
	{
261
		$f = $this->_get($uuid);
262
		return $f->restore();
263
	}
264
265
	/**
266
	 * @inheritdoc
267
	 */
268 2
	public function prepend($uuid, $contents, $separator = PHP_EOL)
269
	{
270 2
		$drive = $this->getDriveFor($uuid, $file);
271 2
		return $drive->prepend($file['path'], $contents, $separator);
272
	}
273
274
	/**
275
	 * @inheritdoc
276
	 */
277 2
	public function append($uuid, $contents, $separator = PHP_EOL)
278
	{
279 2
		$drive = $this->getDriveFor($uuid, $file);
280 2
		return $drive->append($file['path'], $contents, $separator);
281
	}
282
283
	/**
284
	 * @inheritdoc
285
	 */
286 30
	public function getDrive()
287
	{
288 30
		if (empty($this->defaultDrive)) {
289
			throw new InvalidConfigException('You must define a default drive for the File Manager - Set "defaultDrive" configuration option to be one of the Drive Managers configured drives - see the Drive Manager\'s "drives" config option');
290
		}
291 30
		return neon()->firefly->drive($this->defaultDrive);
292
	}
293
294
	/**
295
	 * @inheritdoc
296
	 */
297 22
	public function getDriveFor($uuid, &$file)
298
	{
299 22
		$file = $this->_get($uuid);
300 18
		return neon()->firefly->drive($file->drive);
301
	}
302
303
	/**
304
	 * @inheritdoc
305
	 */
306
	public function sendFile($uuid, $download=false)
307
	{
308
		$meta = $this->getMeta($uuid);
309
		$drive = $this->getDriveFor($uuid, $file);
310
		$read = $this->readStream($uuid);
311
		$meta['mime_type'] = $drive->getMimeType($meta['path']);
312
		// log the download stat
313
		if ($download === true) {
314
			$file->downloaded = $file->downloaded + 1;
315
			$file->save();
316
		}
317
		neon()->response->sendStreamAsFile($read, $meta['path'], [
0 ignored issues
show
Bug introduced by
It seems like $read can also be of type false; however, parameter $handle of yii\web\Response::sendStreamAsFile() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

317
		neon()->response->sendStreamAsFile(/** @scrutinizer ignore-type */ $read, $meta['path'], [
Loading history...
318
			'mimeType' => $meta['mime_type'],
319
			'inline' => !$download
320
		]);
321
		return neon()->response;
322
	}
323
324
	/**
325
	 * @inheritdoc
326
	 */
327 4
	public function getFileHash($uuid, $refresh = false)
328
	{
329 4
		$data = $this->getMeta($uuid);
330 4
		$hash = $data['file_hash'] ? $data['file_hash'] : null;
331 4
		if ($refresh || $hash === null) {
332 4
			$driver = $this->getDriveFor($uuid, $file);
333 4
			$hash = $driver->hash($file['path']);
334 4
			$model = $this->_get($uuid);
335 4
			if (!$hash) return false;
336 4
			$model->file_hash = $hash;
337 4
			$model->save();
338
		}
339 4
		return $hash;
340
	}
341
342
	/**
343
	 * @inheritdoc
344
	 */
345 2
	public function getFileName($uuid)
346
	{
347 2
		$meta = $this->getMeta($uuid);
348 2
		return isset($meta['name']) ? $meta['name'] : false ;
349
	}
350
351
	/**
352
	 * @inheritdoc
353
	 */
354 2
	public function setFileName($uuid, $name)
355
	{
356
		try {
357 2
			$drive = $this->getDriveFor($uuid, $file);
0 ignored issues
show
Unused Code introduced by
The assignment to $drive is dead and can be removed.
Loading history...
358 2
		} catch (FileNotFoundException $e) {
359 2
			return false;
360
		}
361 2
		$file->name = $name;
362 2
		return $file->save();
363
	}
364
365
	/**
366
	 * @inheritdoc
367
	 */
368 10
	public function getSize($uuid)
369
	{
370 10
		$meta = $this->getMeta($uuid);
371 10
		return isset($meta['size']) ? $meta['size'] : false ;
372
	}
373
374
	/**
375
	 * @inheritdoc
376
	 */
377 2
	public function setMeta($uuid, $meta)
378
	{
379
		try {
380 2
			$model = $this->_get($uuid);
381 2
			$model->meta = $meta;
382 2
			return $model->save();
383 2
		} catch (FileNotFoundException $e) {
384 2
			return false;
385
		}
386
	}
387
388
	/**
389
	 * Update file fields
390
	 * 
391
	 * @param string $uuid - the uuid of the file
392
	 * @param array $updates - and array of updates keys matching field names
393
	 * @return array the updated model
394
	 */
395
	public function update($uuid, $updates)
396
	{
397
		$model = $this->_get($uuid);
398
		$model->attributes = $updates;
399
		if (!$model->save())
400
			throw new HttpException(400, $model->getErrors());
0 ignored issues
show
Bug introduced by
$model->getErrors() of type array is incompatible with the type string expected by parameter $message of yii\web\HttpException::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

400
			throw new HttpException(400, /** @scrutinizer ignore-type */ $model->getErrors());
Loading history...
401
		return $model->toArray();
402
	}
403
404
	/**
405
	 * Find a file manager file model by its path and drive location
406
	 *
407
	 * @param string $drive
408
	 * @param string $path
409
	 * @return array|null
410
	 */
411 2
	public function findFile($drive, $path)
412
	{
413 2
		$model = Model::find()->where(['drive' => $drive, 'path' => $path])->one();
414 2
		if ($model === null)
415 2
			return null;
416 2
		return $model->toArray();
417
	}
418
419
	/**
420
	 * Checks the file does not exceed PHP memory limit - and throws an exception.
421
	 *
422
	 * @param $uuid
423
	 * @throws \neon\firefly\services\fileManager\FileExceedsMemoryLimitException
424
	 */
425 10
	protected function _checkFileSizeLimit($uuid)
426
	{
427 10
		if (function_exists('ini_get')) {
428 10
			$bytes = to_bytes(ini_get('memory_limit'));
429 10
			$size = $this->getSize($uuid);
430 10
			if ($size >= $bytes) {
431 2
				throw new FileExceedsMemoryLimitException();
432
			}
433
		}
434 8
	}
435
436
	/**
437
	 * Get the row from the database by uuid.
438
	 * This function is a stub as can add a simple session cache.
439
	 *
440
	 * @param  string $uuid
441
	 * @return  Model
442
	 */
443 36
	protected function _get($uuid)
444
	{
445 36
		return Model::get($uuid);
446
	}
447
448
	/**
449
	 * Auto generate a sensible path
450
	 * By default this will generate path based on the year/month/[fileName]_[uuid].extension
451
	 * where [fileName] is the origin file name supplied
452
	 * and [uuid] is the filemanager uuid key for this file
453
	 * e.g. 2016/11
454
	 *
455
	 * @param  string  $name  - The actual file name
456
	 * @return  string  - The full file past on the drive
457
	 */
458 30
	protected function _generatePath(&$name=null, $fm)
459
	{
460 30
		$info = pathinfo($name);
0 ignored issues
show
Bug introduced by
It seems like $name can also be of type null; however, parameter $path of pathinfo() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

460
		$info = pathinfo(/** @scrutinizer ignore-type */ $name);
Loading history...
461 30
		$fileName = $info['filename'];
462 30
		$extension = isset($info['extension']) ? '.' . $info['extension'] : '';
463 30
		if (empty($fileName)) {
464 6
			$fileName = 'nameless';
465 6
			$name = 'nameless';
466
		}
467 30
		return date('Y') . '/' . date('m') . '/' . $fileName . '.' . $fm->uuid . $extension;
468
	}
469
470
}
471