File::storeMany()   A
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 4
nop 2
dl 0
loc 22
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
namespace Epesi\FileStorage\Models;
4
5
use Illuminate\Support\Facades\Auth;
6
use atk4\data\Model;
7
use Epesi\Core\Data\HasEpesiConnection;
0 ignored issues
show
Bug introduced by
The type Epesi\Core\Data\HasEpesiConnection was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use Epesi\Core\System\User\Database\Models\atk4\User;
9
10
class LinkNotFound extends \Exception {}
11
class LinkDuplicate extends \Exception {}
12
class FileNotFound extends \Exception {}
13
14
class File extends Model
15
{
16
    use HasEpesiConnection;
17
    
18
	public $table = 'filestorage_files';
19
20
    function init() {
21
    	parent::init();
22
    	
23
    	$this->addFields([
24
    	        'created_at' => ['caption' => __('Stored At')],
25
    			'name' => ['caption' => __('File Name')],
26
    			'link' => ['caption' => __('Link')],
27
    			'backref'
28
    	]);
29
    	    	
30
    	$this->hasOne('created_by', [User::class, 'our_field' => 'created_by'])->addTitle(['field' => 'created_by_user', 'caption' => __('Stored By')]);
0 ignored issues
show
Bug introduced by
The method addTitle() does not exist on atk4\data\Reference. It seems like you code against a sub-type of atk4\data\Reference such as atk4\data\Reference\HasOne_SQL. ( Ignorable by Annotation )

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

30
    	$this->hasOne('created_by', [User::class, 'our_field' => 'created_by'])->/** @scrutinizer ignore-call */ addTitle(['field' => 'created_by_user', 'caption' => __('Stored By')]);
Loading history...
31
    	
32
    	$this->hasOne('content', [FileContent::class, 'our_field' => 'content_id']);
33
    	
34
    	$this->hasMany('links', [FileRemoteAccess::class, 'their_field' => 'file_id']);
35
    	
36
    	$this->addCalculatedField('thumbnail', [[__CLASS__, 'getThumbnailField']]);
37
    }
38
39
    public function userActiveLinks()
40
    {
41
    	return $this->ref('links')->addCrits([
42
    			['created_by', Auth::id()],
43
    			['expires_at', '>', date('Y-m-d H:i:s')]
44
    	]);
45
    }
46
        
47
    /**
48
     * Retrieve the file
49
     *
50
     * @param int|string $idOrLink Filestorage ID or unique link string
51
     * @param bool $useCache Use cache or not
52
     *
53
     * @return static
54
     * 
55
     * @throws FileNotFound
56
     */
57
    public static function retrieve($idOrLink)
58
    {
59
    	$id = self::getIdByLink($idOrLink, true, true);
60
61
    	$file = self::create()->tryLoad($id);
62
    	
63
    	if (! $file->ref('content')['hash']) {
64
    		throw new FileNotFound('File object does not have corresponding content');
65
    	}
66
67
    	return $file;
68
    }
69
    
70
    /**
71
     * Get Filestorage ID by link
72
     *
73
     * @param string $link            Unique link
74
     * @param bool   $useCache       Use cache or not
75
     * @param bool   $throwException Throw exception if link is not found
76
     *
77
     * @return int Filestorage ID
78
     * @throws LinkNotFound
79
     */
80
    public static function getIdByLink($link, $useCache = true, $throwException = false)
81
    {
82
    	static $cache = [];
83
    	
84
    	if (is_numeric($link) || is_null($link)) return $link;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $link returns the type string which is incompatible with the documented return type integer.
Loading history...
85
    	
86
    	if (is_object($link)) return $link['id'];
0 ignored issues
show
introduced by
The condition is_object($link) is always false.
Loading history...
87
    	
88
    	if (!$useCache || !isset($cache[$link])) {
89
    		$file = self::create()->tryLoadBy('link', $link);
90
    		
91
    		$cache[$link] = $file? $file->get('id'): null;
92
    		
93
    		if (!$cache[$link] && $throwException) {
94
    			throw new LinkNotFound($link);
95
    		}
96
    	}
97
    	
98
    	return $cache[$link];
99
    }
100
101
    /**
102
     * Mark file as deleted. Does not remove any content!
103
     *
104
     * @param int|string $idOrLink Filestorage ID or unique link
105
     */
106
    public static function unlink($idOrLink)
107
    {
108
   		if ($id = self::getIdByLink($idOrLink, false)) {
109
   			self::create()->delete($id);
110
    	}
111
    }
112
    
113
    /**
114
     * Check if file exists
115
     *
116
     * @param int|static $idOrMeta              Filestorage ID or file object
117
     * @param bool      $throwException Throw exception on missing file or return false
118
     *
119
     * @return bool True if file exists, false otherwise
120
     * @throws FileNotFound May be thrown if $throwException set to true
121
     */
122
    public static function exists($idOrMeta, $throwException = false)
123
    {
124
    	try {
125
    		$file = is_numeric($idOrMeta) ? self::retrieve($idOrMeta) : $idOrMeta;
126
    		
127
    		if (! file_exists($file->ref('content')['path'])) {
128
    			throw new FileNotFound('Exception - file not found: ' . $file->ref('content')['path']);
129
    		}
130
    	} catch (\Exception $exception) {
131
    		if ($throwException)
132
    			throw $exception;
133
    		else
134
    			return false;
135
    	}
136
    	
137
    	return true;
138
    }
139
    
140
    /**
141
     * Add multiple files, clone file if file id is provided.
142
     * May be used to update backref for all files.
143
     *
144
     * @param array $files array of existing filestorage ids or array with values for the new file
145
     * @param string|null $backref Backref for all files
146
     * @return array Newly created Meta Ids sorted in ascending order
147
     */
148
    public static function storeMany($files, $backref = null)
149
    {
150
    	$ids = [];
151
    	foreach ((array) $files as $filePath) {
152
    		if (! is_numeric($filePath)) {
153
    			$ids[] = self::store($filePath);
154
    			
155
    			continue;
156
    		}
157
    		
158
    		$file = self::retrieve($filePath, false);
0 ignored issues
show
Unused Code introduced by
The call to Epesi\FileStorage\Models\File::retrieve() has too many arguments starting with false. ( Ignorable by Annotation )

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

158
    		/** @scrutinizer ignore-call */ 
159
      $file = self::retrieve($filePath, false);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
159
    			
160
    		if ($backref && $file['backref'] != $backref) {
161
    			$file->save(compact('backref'));
162
    		}
163
    			
164
    		$ids[] = $file['id'];
165
    	}
166
    	
167
    	sort($ids);
168
    	
169
    	return $ids;
170
    }
171
        
172
    /**
173
     * @param string|array     $fileOrPath
174
     * @param string     $content    Content of the file
175
     * 
176
     * @return int Filestorage ID
177
     * @throws LinkDuplicate
178
     */
179
    public static function store($fileOrPath, $content = null) 
180
    {
181
    	$file = $fileOrPath;
182
    	
183
    	if (is_object($file)) {
0 ignored issues
show
introduced by
The condition is_object($file) is always false.
Loading history...
184
    		$file->action('update', [
185
    				'content_id' => FileContent::store($content)
186
    		]);
187
    		
188
    		return $file['id'];
189
    	}
190
    	    	
191
    	if (! $content && is_string($fileOrPath)) {
192
    		$content = file_get_contents($fileOrPath);
193
    		
194
    		$file = [
195
    				'name' => basename($fileOrPath)
196
    		];
197
    	}
198
    	
199
    	if (! empty($file['link']) && self::getIdByLink($file['link'], false)) {
200
    		throw new LinkDuplicate($file['link']);
201
    	}
202
    	
203
    	$content = $file['content']?? $content;
204
    	
205
    	if (is_array($content)) {
206
    		$path = $content['path'];
207
    			
208
    		$file['name'] = $file['name']?? basename($path);
209
    			
210
    		$content = file_get_contents($path);
211
    	}
212
213
    	unset($file['content']);
214
215
   		return self::create()->insert(array_merge([
216
   				'created_at' => date('Y-m-d H:i:s'),
217
   				'created_by' => Auth::id(),
218
   				'content_id' => FileContent::store($content)
219
   		], $file));
220
    }
221
    
222
    public static function getThumbnailField($model)
223
    {
224
    	if (! $model->thumbnailPossible()) return false;
225
    	
226
    	$image = new \Imagick($model->ref('content')['path']  . '[0]');
227
    	
228
    	$image->setImageFormat('jpg');
229
    	
230
    	return collect([
231
    			'mime' => 'image/jpeg',
232
    			'name' => 'preview.jpeg',
233
    			'contents' => $image . ''
234
    	]);
235
    }
236
    
237
    public function thumbnailPossible() {
238
    	return $this->ref('content')['type'] == 'application/pdf' && class_exists('Imagick');
239
    }
240
}