Passed
Push — 2.3.0 ( ...813ccf )
by steve
17:15
created

FileManager::update()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
ccs 0
cts 6
cp 0
crap 6
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
		$q = new Query();
109
		foreach ($all as $i => $field) {
110
			if ($i === 0) {
111
				$q->select(['_uuid'],"'$field[class_type]' as 'class',")
112
					->from('ddt_' . $field['class_type'])
113
					->where([$field['member_ref'] => $uuid]);
114
			} else {
115
				$q->union((new Query())->select(['_uuid'],"'$field[class_type]' as 'class',")
116
					->from('ddt_' . $field['class_type'])
117
					->where([$field['member_ref'] => $uuid]));
118
			}
119
		}
120
		return $q->all();
121
	}
122
123
	/**
124
	 * @inheritdoc
125
	 */
126 28
	public function getMeta($uuid)
127
	{
128
		try {
129 28
			$model = $this->_get($uuid);
130 2
		} catch (FileNotFoundException $e) {
131 2
			return [];
132
		}
133 26
		return $model->toArray();
134
	}
135
136
	/**
137
	 * @inheritdoc
138
	 */
139 2
	public function exists($uuid)
140
	{
141
		try {
142 2
			$this->_get($uuid);
143 2
		} catch (FileNotFoundException $e) {
144 2
			return false;
145
		}
146 2
		return true;
147
	}
148
149
	/**
150
	 * @inheritdoc
151
	 */
152 12
	public function read($uuid)
153
	{
154
		try {
155 12
			$drive = $this->getDriveFor($uuid, $file);
156 2
		} catch (FileNotFoundException $e) {
157 2
			return false;
158
		}
159 10
		$this->_checkFileSizeLimit($uuid);
160 8
		return $drive->read($file['path']);
161
	}
162
163
	/**
164
	 * @inheritdoc
165
	 */
166 4
	public function readStream($uuid)
167
	{
168
		try {
169 4
			$drive = $this->getDriveFor($uuid, $file);
170 2
		} catch (FileNotFoundException $e) {
171 2
			return false;
172
		}
173 2
		return $drive->readStream($file['path']);
174
	}
175
176
	/**
177
	 * @inheritdoc
178
	 */
179
	public function getUrl($uuid)
180
	{
181
		return  Url::to(['/firefly/file/get', 'id' => $uuid], true);
182
	}
183
184
	/**
185
	 * @inheritdoc
186
	 */
187
	public function getImage($uuid, $params=[])
188
	{
189
		$p = array_merge(['/firefly/file/img', 'id'=>$uuid], $params);
190
		return Url::to($p, true);
191
	}
192
193
	/**
194
	 * @inheritdoc
195
	 */
196 2
	public function getVisibility($uuid)
197
	{
198
		try {
199 2
			$drive = $this->getDriveFor($uuid, $file);
200
		} catch (FileNotFoundException $e) {
201
			return false;
202
		}
203 2
		return $drive->getVisibility($file['path']);
204
	}
205
206
	/**
207
	 * @inheritdoc
208
	 */
209 2
	public function setVisibility($uuid, $visibility)
210
	{
211
		try {
212 2
			$drive = $this->getDriveFor($uuid, $file);
213
		} catch (FileNotFoundException $e) {
214
			return false;
215
		}
216 2
		return $drive->setVisibility($file['path'], $visibility);
217
	}
218
219
	/**
220
	 * @inheritdoc
221
	 */
222
	public function copy($uuid)
223
	{
224
		$model = $this->_get($uuid);
225
		$content = neon()->firefly->readStream($uuid);
226
		$newUuid = neon()->firefly->save($content, $model->name, $model->meta);
227
		return $newUuid;
228
	}
229
230
	/**
231
	 * @inheritdoc
232
	 */
233 2
	public function delete($uuid)
234
	{
235
		try {
236 2
			$f = $this->_get($uuid);
237
			return $f->delete();
238 2
		} catch(FileNotFoundException $e) {
239 2
			return false;
240
		}
241
	}
242
243
	/**
244
	 * @inheritdoc
245
	 */
246 6
	public function destroy($uuid)
247
	{
248 6
		$file = Model::findWithDeleted()->where(['uuid' => $uuid])->one();
249 6
		if ($file === null) return false;
250 6
		return $file->destroy();
251
	}
252
253
	/**
254
	 * @inheritdoc
255
	 */
256
	public function restore($uuid)
257
	{
258
		$f = $this->_get($uuid);
259
		return $f->restore();
260
	}
261
262
	/**
263
	 * @inheritdoc
264
	 */
265 2
	public function prepend($uuid, $contents, $separator = PHP_EOL)
266
	{
267 2
		$drive = $this->getDriveFor($uuid, $file);
268 2
		return $drive->prepend($file['path'], $contents, $separator);
269
	}
270
271
	/**
272
	 * @inheritdoc
273
	 */
274 2
	public function append($uuid, $contents, $separator = PHP_EOL)
275
	{
276 2
		$drive = $this->getDriveFor($uuid, $file);
277 2
		return $drive->append($file['path'], $contents, $separator);
278
	}
279
280
	/**
281
	 * @inheritdoc
282
	 */
283 30
	public function getDrive()
284
	{
285 30
		if (empty($this->defaultDrive)) {
286
			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');
287
		}
288 30
		return neon()->firefly->drive($this->defaultDrive);
289
	}
290
291
	/**
292
	 * @inheritdoc
293
	 */
294 22
	public function getDriveFor($uuid, &$file)
295
	{
296 22
		$file = $this->_get($uuid);
297 18
		return neon()->firefly->drive($file->drive);
298
	}
299
300
	/**
301
	 * @inheritdoc
302
	 */
303
	public function sendFile($uuid, $download=false)
304
	{
305
		$meta = $this->getMeta($uuid);
306
		$drive = $this->getDriveFor($uuid, $file);
307
		$read = $this->readStream($uuid);
308
		$meta['mime_type'] = $drive->getMimeType($meta['path']);
309
		// log the download stat
310
		if ($download === true) {
311
			$file->downloaded = $file->downloaded + 1;
312
			$file->save();
313
		}
314
		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

314
		neon()->response->sendStreamAsFile(/** @scrutinizer ignore-type */ $read, $meta['path'], [
Loading history...
315
			'mimeType' => $meta['mime_type'],
316
			'inline' => !$download
317
		]);
318
		return neon()->response;
319
	}
320
321
	/**
322
	 * @inheritdoc
323
	 */
324 4
	public function getFileHash($uuid, $refresh = false)
325
	{
326 4
		$data = $this->getMeta($uuid);
327 4
		$hash = $data['file_hash'] ? $data['file_hash'] : null;
328 4
		if ($refresh || $hash === null) {
329 4
			$driver = $this->getDriveFor($uuid, $file);
330 4
			$hash = $driver->hash($file['path']);
331 4
			$model = $this->_get($uuid);
332 4
			if (!$hash) return false;
333 4
			$model->file_hash = $hash;
334 4
			$model->save();
335
		}
336 4
		return $hash;
337
	}
338
339
	/**
340
	 * @inheritdoc
341
	 */
342 2
	public function getFileName($uuid)
343
	{
344 2
		$meta = $this->getMeta($uuid);
345 2
		return isset($meta['name']) ? $meta['name'] : false ;
346
	}
347
348
	/**
349
	 * @inheritdoc
350
	 */
351 2
	public function setFileName($uuid, $name)
352
	{
353
		try {
354 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...
355 2
		} catch (FileNotFoundException $e) {
356 2
			return false;
357
		}
358 2
		$file->name = $name;
359 2
		return $file->save();
360
	}
361
362
	/**
363
	 * @inheritdoc
364
	 */
365 10
	public function getSize($uuid)
366
	{
367 10
		$meta = $this->getMeta($uuid);
368 10
		return isset($meta['size']) ? $meta['size'] : false ;
369
	}
370
371
	/**
372
	 * @inheritdoc
373
	 */
374 2
	public function setMeta($uuid, $meta)
375
	{
376
		try {
377 2
			$model = $this->_get($uuid);
378 2
			$model->meta = $meta;
379 2
			return $model->save();
380 2
		} catch (FileNotFoundException $e) {
381 2
			return false;
382
		}
383
	}
384
385
	/**
386
	 * Update file fields
387
	 * 
388
	 * @param string $uuid - the uuid of the file
389
	 * @param array $updates - and array of updates keys matching field names
390
	 * @return array the updated model
391
	 */
392
	public function update($uuid, $updates)
393
	{
394
		$model = $this->_get($uuid);
395
		$model->attributes = $updates;
396
		if (!$model->save())
397
			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

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