]> BookStack Code Mirror - bookstack/blob - app/Uploads/UserAvatars.php
e98c1cfcaf5fa19488415f9237b00d4b46d12afc
[bookstack] / app / Uploads / UserAvatars.php
1 <?php namespace BookStack\Uploads;
2
3 use BookStack\Auth\User;
4 use BookStack\Exceptions\HttpFetchException;
5 use Exception;
6 use Illuminate\Support\Facades\Log;
7
8 class UserAvatars
9 {
10     protected $imageService;
11     protected $http;
12
13     public function __construct(ImageService $imageService, HttpFetcher $http)
14     {
15         $this->imageService = $imageService;
16         $this->http = $http;
17     }
18
19     /**
20      * Fetch and assign an avatar image to the given user.
21      */
22     public function fetchAndAssignToUser(User $user): void
23     {
24         if (!$this->avatarFetchEnabled()) {
25             return;
26         }
27
28         try {
29             $this->destroyAllForUser($user);
30             $avatar = $this->saveAvatarImage($user);
31             $user->avatar()->associate($avatar);
32             $user->save();
33         } catch (Exception $e) {
34             Log::error('Failed to save user avatar image');
35         }
36     }
37
38     /**
39      * Assign a new avatar image to the given user using the given image data.
40      */
41     public function assignToUserFromExistingData(User $user, string $imageData, string $extension): void
42     {
43         try {
44             $this->destroyAllForUser($user);
45             $avatar = $this->createAvatarImageFromData($user, $imageData, $extension);
46             $user->avatar()->associate($avatar);
47             $user->save();
48         } catch (Exception $e) {
49             Log::error('Failed to save user avatar image');
50         }
51     }
52
53     /**
54      * Destroy all user avatars uploaded to the given user.
55      */
56     public function destroyAllForUser(User $user)
57     {
58         $profileImages = Image::query()->where('type', '=', 'user')
59             ->where('uploaded_to', '=', $user->id)
60             ->get();
61
62         foreach ($profileImages as $image) {
63             $this->imageService->destroy($image);
64         }
65     }
66
67     /**
68      * Save an avatar image from an external service.
69      * @throws Exception
70      */
71     protected function saveAvatarImage(User $user, int $size = 500): Image
72     {
73         $avatarUrl = $this->getAvatarUrl();
74         $email = strtolower(trim($user->email));
75
76         $replacements = [
77             '${hash}' => md5($email),
78             '${size}' => $size,
79             '${email}' => urlencode($email),
80         ];
81
82         $userAvatarUrl = strtr($avatarUrl, $replacements);
83         $imageData = $this->getAvatarImageData($userAvatarUrl);
84         return $this->createAvatarImageFromData($user, $imageData, 'png');
85     }
86
87     /**
88      * Creates a new image instance and saves it in the system as a new user avatar image.
89      */
90     protected function createAvatarImageFromData(User $user, string $imageData, string $extension): Image
91     {
92         $imageName = str_replace(' ', '-', $user->id . '-avatar.' . $extension);
93
94         $image = $this->imageService->saveNew($imageName, $imageData, 'user', $user->id);
95         $image->created_by = $user->id;
96         $image->updated_by = $user->id;
97         $image->save();
98
99         return $image;
100     }
101
102     /**
103      * Gets an image from url and returns it as a string of image data.
104      * @throws Exception
105      */
106     protected function getAvatarImageData(string $url): string
107     {
108         try {
109             $imageData = $this->http->fetch($url);
110         } catch (HttpFetchException $exception) {
111             throw new Exception(trans('errors.cannot_get_image_from_url', ['url' => $url]));
112         }
113         return $imageData;
114     }
115
116     /**
117      * Check if fetching external avatars is enabled.
118      */
119     protected function avatarFetchEnabled(): bool
120     {
121         $fetchUrl = $this->getAvatarUrl();
122         return is_string($fetchUrl) && strpos($fetchUrl, 'http') === 0;
123     }
124
125     /**
126      * Get the URL to fetch avatars from.
127      */
128     protected function getAvatarUrl(): string
129     {
130         $url = trim(config('services.avatar_url'));
131
132         if (empty($url) && !config('services.disable_services')) {
133             $url = 'https://www.gravatar.com/avatar/${hash}?s=${size}&d=identicon';
134         }
135
136         return $url;
137     }
138 }