UserAvatar::getFile()   F
last analyzed

Complexity

Conditions 16
Paths 342

Size

Total Lines 65
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 46
nc 342
nop 2
dl 0
loc 65
rs 2.9583
c 0
b 0
f 0

How to fix   Long Method    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
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2018, Michael Weimann <[email protected]>
7
 *
8
 * @author Arthur Schiwon <[email protected]>
9
 * @author Christoph Wurst <[email protected]>
10
 * @author Joas Schilling <[email protected]>
11
 * @author Michael Weimann <[email protected]>
12
 * @author Vincent Petry <[email protected]>
13
 *
14
 * @license GNU AGPL version 3 or any later version
15
 *
16
 * This program is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License as
18
 * published by the Free Software Foundation, either version 3 of the
19
 * License, or (at your option) any later version.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License
27
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
28
 *
29
 */
30
namespace OC\Avatar;
31
32
use OC\NotSquareException;
33
use OC\User\User;
34
use OCP\Files\NotFoundException;
35
use OCP\Files\NotPermittedException;
36
use OCP\Files\SimpleFS\ISimpleFile;
37
use OCP\Files\SimpleFS\ISimpleFolder;
38
use OCP\IConfig;
39
use OCP\IImage;
40
use OCP\IL10N;
41
use Psr\Log\LoggerInterface;
42
43
/**
44
 * This class represents a registered user's avatar.
45
 */
46
class UserAvatar extends Avatar {
47
	private IConfig $config;
48
	private ISimpleFolder $folder;
49
	private IL10N $l;
50
	private User $user;
51
52
	/**
53
	 * UserAvatar constructor.
54
	 *
55
	 * @param IConfig $config The configuration
56
	 * @param ISimpleFolder $folder The avatar files folder
57
	 * @param IL10N $l The localization helper
58
	 * @param User $user The user this class manages the avatar for
59
	 * @param LoggerInterface $logger The logger
60
	 */
61
	public function __construct(
62
		ISimpleFolder $folder,
63
		IL10N $l,
64
		User $user,
65
		LoggerInterface $logger,
66
		IConfig $config) {
67
		parent::__construct($logger);
68
		$this->folder = $folder;
69
		$this->l = $l;
70
		$this->user = $user;
71
		$this->config = $config;
72
	}
73
74
	/**
75
	 * Check if an avatar exists for the user
76
	 */
77
	public function exists(): bool {
78
		return $this->folder->fileExists('avatar.jpg') || $this->folder->fileExists('avatar.png');
79
	}
80
81
	/**
82
	 * Sets the users avatar.
83
	 *
84
	 * @param IImage|resource|string $data An image object, imagedata or path to set a new avatar
85
	 * @throws \Exception if the provided file is not a jpg or png image
86
	 * @throws \Exception if the provided image is not valid
87
	 * @throws NotSquareException if the image is not square
88
	 * @return void
89
	 */
90
	public function set($data): void {
91
		$img = $this->getAvatarImage($data);
92
		$data = $img->data();
93
94
		$this->validateAvatar($img);
95
96
		$this->remove(true);
97
		$type = $this->getAvatarImageType($img);
98
		$file = $this->folder->newFile('avatar.' . $type);
99
		$file->putContent($data);
100
101
		try {
102
			$generated = $this->folder->getFile('generated');
103
			$generated->delete();
104
		} catch (NotFoundException $e) {
105
			//
106
		}
107
108
		$this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', 'false');
109
		$this->user->triggerChange('avatar', $file);
110
	}
111
112
	/**
113
	 * Returns an image from several sources.
114
	 *
115
	 * @param IImage|resource|string|\GdImage $data An image object, imagedata or path to the avatar
116
	 * @return IImage
117
	 */
118
	private function getAvatarImage($data): IImage {
119
		if ($data instanceof IImage) {
120
			return $data;
121
		}
122
123
		$img = new \OCP\Image();
124
		if (
125
			(is_resource($data) && get_resource_type($data) === 'gd') ||
126
			(is_object($data) && get_class($data) === \GdImage::class)
127
		) {
128
			$img->setResource($data);
129
		} elseif (is_resource($data)) {
130
			$img->loadFromFileHandle($data);
131
		} else {
132
			try {
133
				// detect if it is a path or maybe the images as string
134
				$result = @realpath($data);
135
				if ($result === false || $result === null) {
136
					$img->loadFromData($data);
137
				} else {
138
					$img->loadFromFile($data);
139
				}
140
			} catch (\Error $e) {
141
				$img->loadFromData($data);
142
			}
143
		}
144
145
		return $img;
146
	}
147
148
	/**
149
	 * Returns the avatar image type.
150
	 */
151
	private function getAvatarImageType(IImage $avatar): string {
152
		$type = substr($avatar->mimeType(), -3);
153
		if ($type === 'peg') {
154
			$type = 'jpg';
155
		}
156
		return $type;
157
	}
158
159
	/**
160
	 * Validates an avatar image:
161
	 * - must be "png" or "jpg"
162
	 * - must be "valid"
163
	 * - must be in square format
164
	 *
165
	 * @param IImage $avatar The avatar to validate
166
	 * @throws \Exception if the provided file is not a jpg or png image
167
	 * @throws \Exception if the provided image is not valid
168
	 * @throws NotSquareException if the image is not square
169
	 */
170
	private function validateAvatar(IImage $avatar): void {
171
		$type = $this->getAvatarImageType($avatar);
172
173
		if ($type !== 'jpg' && $type !== 'png') {
174
			throw new \Exception($this->l->t('Unknown filetype'));
175
		}
176
177
		if (!$avatar->valid()) {
178
			throw new \Exception($this->l->t('Invalid image'));
179
		}
180
181
		if (!($avatar->height() === $avatar->width())) {
182
			throw new NotSquareException($this->l->t('Avatar image is not square'));
183
		}
184
	}
185
186
	/**
187
	 * Removes the users avatar.
188
	 * @throws \OCP\Files\NotPermittedException
189
	 * @throws \OCP\PreConditionNotMetException
190
	 */
191
	public function remove(bool $silent = false): void {
192
		$avatars = $this->folder->getDirectoryListing();
193
194
		$this->config->setUserValue($this->user->getUID(), 'avatar', 'version',
195
			(string)((int)$this->config->getUserValue($this->user->getUID(), 'avatar', 'version', '0') + 1));
196
197
		foreach ($avatars as $avatar) {
198
			$avatar->delete();
199
		}
200
		$this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', 'true');
201
		if (!$silent) {
202
			$this->user->triggerChange('avatar', '');
203
		}
204
	}
205
206
	/**
207
	 * Get the extension of the avatar. If there is no avatar throw Exception
208
	 *
209
	 * @throws NotFoundException
210
	 */
211
	private function getExtension(bool $generated, bool $darkTheme): string {
212
		if ($darkTheme && !$generated) {
213
			if ($this->folder->fileExists('avatar-dark.jpg')) {
214
				return 'jpg';
215
			} elseif ($this->folder->fileExists('avatar-dark.png')) {
216
				return 'png';
217
			}
218
		}
219
		if ($this->folder->fileExists('avatar.jpg')) {
220
			return 'jpg';
221
		} elseif ($this->folder->fileExists('avatar.png')) {
222
			return 'png';
223
		}
224
		throw new NotFoundException;
225
	}
226
227
	/**
228
	 * Returns the avatar for an user.
229
	 *
230
	 * If there is no avatar file yet, one is generated.
231
	 *
232
	 * @param int $size
233
	 * @return ISimpleFile
234
	 * @throws NotFoundException
235
	 * @throws \OCP\Files\NotPermittedException
236
	 * @throws \OCP\PreConditionNotMetException
237
	 */
238
	public function getFile(int $size, bool $darkTheme = false): ISimpleFile {
239
		$generated = $this->folder->fileExists('generated');
240
241
		try {
242
			$ext = $this->getExtension($generated, $darkTheme);
243
		} catch (NotFoundException $e) {
244
			if (!$data = $this->generateAvatarFromSvg(1024, $darkTheme)) {
245
				$data = $this->generateAvatar($this->getDisplayName(), 1024, $darkTheme);
246
			}
247
			$avatar = $this->folder->newFile($darkTheme ? 'avatar-dark.png' : 'avatar.png');
248
			$avatar->putContent($data);
249
			$ext = 'png';
250
251
			$this->folder->newFile('generated', '');
252
			$this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', 'true');
253
			$generated = true;
254
		}
255
256
		if ($generated) {
257
			if ($size === -1) {
258
				$path = 'avatar' . ($darkTheme ? '-dark' : '') . '.' . $ext;
259
			} else {
260
				$path = 'avatar' . ($darkTheme ? '-dark' : '') . '.'  . $size . '.' . $ext;
261
			}
262
		} else {
263
			if ($size === -1) {
264
				$path = 'avatar.' . $ext;
265
			} else {
266
				$path = 'avatar.' . $size . '.' . $ext;
267
			}
268
		}
269
270
		try {
271
			$file = $this->folder->getFile($path);
272
		} catch (NotFoundException $e) {
273
			if ($size <= 0) {
274
				throw new NotFoundException;
275
			}
276
			if ($generated) {
277
				if (!$data = $this->generateAvatarFromSvg($size, $darkTheme)) {
278
					$data = $this->generateAvatar($this->getDisplayName(), $size, $darkTheme);
279
				}
280
			} else {
281
				$avatar = new \OCP\Image();
282
				$file = $this->folder->getFile('avatar.' . $ext);
283
				$avatar->loadFromData($file->getContent());
284
				$avatar->resize($size);
285
				$data = $avatar->data();
286
			}
287
288
			try {
289
				$file = $this->folder->newFile($path);
290
				$file->putContent($data);
291
			} catch (NotPermittedException $e) {
292
				$this->logger->error('Failed to save avatar for ' . $this->user->getUID());
293
				throw new NotFoundException();
294
			}
295
		}
296
297
		if ($this->config->getUserValue($this->user->getUID(), 'avatar', 'generated', null) === null) {
0 ignored issues
show
introduced by
The condition $this->config->getUserVa...erated', null) === null is always false.
Loading history...
298
			$generated = $generated ? 'true' : 'false';
299
			$this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', $generated);
300
		}
301
302
		return $file;
303
	}
304
305
	/**
306
	 * Returns the user display name.
307
	 */
308
	public function getDisplayName(): string {
309
		return $this->user->getDisplayName();
310
	}
311
312
	/**
313
	 * Handles user changes.
314
	 *
315
	 * @param string $feature The changed feature
316
	 * @param mixed $oldValue The previous value
317
	 * @param mixed $newValue The new value
318
	 * @throws NotPermittedException
319
	 * @throws \OCP\PreConditionNotMetException
320
	 */
321
	public function userChanged(string $feature, $oldValue, $newValue): void {
322
		// If the avatar is not generated (so an uploaded image) we skip this
323
		if (!$this->folder->fileExists('generated')) {
324
			return;
325
		}
326
327
		$this->remove();
328
	}
329
330
	/**
331
	 * Check if the avatar of a user is a custom uploaded one
332
	 */
333
	public function isCustomAvatar(): bool {
334
		return $this->config->getUserValue($this->user->getUID(), 'avatar', 'generated', 'false') !== 'true';
335
	}
336
}
337