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