- Move event listener to new event handling - Add typing almost everywhere - Fix inconsistent interface parameter Signed-off-by: Carl Schwan <carl@carlschwan.eu>tags/v25.0.0beta1
@@ -2497,12 +2497,6 @@ | |||
<ImplementedReturnTypeMismatch occurrences="1"> | |||
<code>Color</code> | |||
</ImplementedReturnTypeMismatch> | |||
<InvalidReturnStatement occurrences="1"> | |||
<code>$finalPalette[$this->hashToInt($hash, $steps * 3)]</code> | |||
</InvalidReturnStatement> | |||
<InvalidReturnType occurrences="1"> | |||
<code>Color</code> | |||
</InvalidReturnType> | |||
<ParamNameMismatch occurrences="1"> | |||
<code>$hash</code> | |||
</ParamNameMismatch> |
@@ -1548,6 +1548,8 @@ return array( | |||
'OC\\User\\Database' => $baseDir . '/lib/private/User/Database.php', | |||
'OC\\User\\DisplayNameCache' => $baseDir . '/lib/private/User/DisplayNameCache.php', | |||
'OC\\User\\LazyUser' => $baseDir . '/lib/private/User/LazyUser.php', | |||
'OC\\User\\Listeners\\UserChangedListener' => $baseDir . '/lib/private/User/Listeners/UserChangedListener.php', | |||
'OC\\User\\Listeners\\UserDeletedListener' => $baseDir . '/lib/private/User/Listeners/UserDeletedListener.php', | |||
'OC\\User\\LoginException' => $baseDir . '/lib/private/User/LoginException.php', | |||
'OC\\User\\Manager' => $baseDir . '/lib/private/User/Manager.php', | |||
'OC\\User\\NoUserException' => $baseDir . '/lib/private/User/NoUserException.php', |
@@ -1581,6 +1581,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 | |||
'OC\\User\\Database' => __DIR__ . '/../../..' . '/lib/private/User/Database.php', | |||
'OC\\User\\DisplayNameCache' => __DIR__ . '/../../..' . '/lib/private/User/DisplayNameCache.php', | |||
'OC\\User\\LazyUser' => __DIR__ . '/../../..' . '/lib/private/User/LazyUser.php', | |||
'OC\\User\\Listeners\\UserChangedListener' => __DIR__ . '/../../..' . '/lib/private/User/Listeners/UserChangedListener.php', | |||
'OC\\User\\Listeners\\UserDeletedListener' => __DIR__ . '/../../..' . '/lib/private/User/Listeners/UserDeletedListener.php', | |||
'OC\\User\\LoginException' => __DIR__ . '/../../..' . '/lib/private/User/LoginException.php', | |||
'OC\\User\\Manager' => __DIR__ . '/../../..' . '/lib/private/User/Manager.php', | |||
'OC\\User\\NoUserException' => __DIR__ . '/../../..' . '/lib/private/User/NoUserException.php', |
@@ -46,9 +46,7 @@ use Psr\Log\LoggerInterface; | |||
* This class gets and sets users avatars. | |||
*/ | |||
abstract class Avatar implements IAvatar { | |||
/** @var LoggerInterface */ | |||
protected $logger; | |||
protected LoggerInterface $logger; | |||
/** | |||
* https://github.com/sebdesign/cap-height -- for 500px height | |||
@@ -57,10 +55,8 @@ abstract class Avatar implements IAvatar { | |||
* (0.4 letter-to-total-height ratio, 500*0.4=200), so: 200/0.715 = 280px. | |||
* Since we start from the baseline (text-anchor) we need to | |||
* shift the y axis by 100px (half the caps height): 500/2+100=350 | |||
* | |||
* @var string | |||
*/ | |||
private $svgTemplate = '<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
private string $svgTemplate = '<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg width="{size}" height="{size}" version="1.1" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg"> | |||
<rect width="100%" height="100%" fill="#{fill}"></rect> | |||
<text x="50%" y="350" style="font-weight:normal;font-size:280px;font-family:\'Noto Sans\';text-anchor:middle;fill:#fff">{letter}</text> | |||
@@ -72,15 +68,11 @@ abstract class Avatar implements IAvatar { | |||
/** | |||
* Returns the user display name. | |||
* | |||
* @return string | |||
*/ | |||
abstract public function getDisplayName(): string; | |||
/** | |||
* Returns the first letter of the display name, or "?" if no name given. | |||
* | |||
* @return string | |||
*/ | |||
private function getAvatarText(): string { | |||
$displayName = $this->getDisplayName(); | |||
@@ -96,9 +88,7 @@ abstract class Avatar implements IAvatar { | |||
/** | |||
* @inheritdoc | |||
*/ | |||
public function get($size = 64) { | |||
$size = (int) $size; | |||
public function get(int $size = 64) { | |||
try { | |||
$file = $this->getFile($size); | |||
} catch (NotFoundException $e) { | |||
@@ -158,12 +148,8 @@ abstract class Avatar implements IAvatar { | |||
/** | |||
* Generate png avatar with GD | |||
* | |||
* @param string $userDisplayName | |||
* @param int $size | |||
* @return string | |||
*/ | |||
protected function generateAvatar($userDisplayName, $size) { | |||
protected function generateAvatar(string $userDisplayName, int $size): string { | |||
$text = $this->getAvatarText(); | |||
$backgroundColor = $this->avatarBackgroundColor($userDisplayName); | |||
@@ -209,7 +195,7 @@ abstract class Avatar implements IAvatar { | |||
string $text, | |||
string $font, | |||
int $size, | |||
$angle = 0 | |||
int $angle = 0 | |||
): array { | |||
// Image width & height | |||
$xi = imagesx($image); | |||
@@ -231,11 +217,11 @@ abstract class Avatar implements IAvatar { | |||
/** | |||
* Calculate steps between two Colors | |||
* @param object Color $steps start color | |||
* @param object Color $ends end color | |||
* @return array [r,g,b] steps for each color to go from $steps to $ends | |||
* @param int $steps start color | |||
* @param Color[] $ends end color | |||
* @return array{0: int, 1: int, 2: int} [r,g,b] steps for each color to go from $steps to $ends | |||
*/ | |||
private function stepCalc($steps, $ends) { | |||
private function stepCalc(int $steps, array $ends): array { | |||
$step = []; | |||
$step[0] = ($ends[1]->r - $ends[0]->r) / $steps; | |||
$step[1] = ($ends[1]->g - $ends[0]->g) / $steps; | |||
@@ -244,12 +230,11 @@ abstract class Avatar implements IAvatar { | |||
} | |||
/** | |||
* Convert a string to an integer evenly | |||
* @param string $hash the text to parse | |||
* @param int $maximum the maximum range | |||
* @return int[] between 0 and $maximum | |||
* Mix two colors | |||
* | |||
* @return Color[] | |||
*/ | |||
private function mixPalette($steps, $color1, $color2) { | |||
private function mixPalette($steps, Color $color1, Color $color2) { | |||
$palette = [$color1]; | |||
$step = $this->stepCalc($steps, [$color1, $color2]); | |||
for ($i = 1; $i < $steps; $i++) { | |||
@@ -267,7 +252,7 @@ abstract class Avatar implements IAvatar { | |||
* @param int $maximum the maximum range | |||
* @return int between 0 and $maximum | |||
*/ | |||
private function hashToInt($hash, $maximum) { | |||
private function hashToInt(string $hash, int $maximum): int { | |||
$final = 0; | |||
$result = []; | |||
@@ -285,10 +270,9 @@ abstract class Avatar implements IAvatar { | |||
} | |||
/** | |||
* @param string $hash | |||
* @return Color Object containting r g b int in the range [0, 255] | |||
*/ | |||
public function avatarBackgroundColor(string $hash) { | |||
public function avatarBackgroundColor(string $hash): Color { | |||
// Normalize hash | |||
$hash = strtolower($hash); | |||
@@ -26,6 +26,7 @@ declare(strict_types=1); | |||
*/ | |||
namespace OC\Avatar; | |||
use OCP\Files\SimpleFS\ISimpleFile; | |||
use OCP\Files\SimpleFS\InMemoryFile; | |||
use Psr\Log\LoggerInterface; | |||
@@ -35,16 +36,13 @@ use Psr\Log\LoggerInterface; | |||
class GuestAvatar extends Avatar { | |||
/** | |||
* Holds the guest user display name. | |||
* | |||
* @var string | |||
*/ | |||
private $userDisplayName; | |||
private string $userDisplayName; | |||
/** | |||
* GuestAvatar constructor. | |||
* | |||
* @param string $userDisplayName The guest user display name | |||
* @param LoggerInterface $logger The logger | |||
*/ | |||
public function __construct(string $userDisplayName, LoggerInterface $logger) { | |||
parent::__construct($logger); | |||
@@ -53,17 +51,13 @@ class GuestAvatar extends Avatar { | |||
/** | |||
* Tests if the user has an avatar. | |||
* | |||
* @return true Guests always have an avatar. | |||
*/ | |||
public function exists() { | |||
return true; | |||
public function exists(): bool { | |||
return true; // Guests always have an avatar. | |||
} | |||
/** | |||
* Returns the guest user display name. | |||
* | |||
* @return string | |||
*/ | |||
public function getDisplayName(): string { | |||
return $this->userDisplayName; | |||
@@ -75,24 +69,21 @@ class GuestAvatar extends Avatar { | |||
* @param \OCP\IImage|resource|string $data | |||
* @return void | |||
*/ | |||
public function set($data) { | |||
public function set($data): void { | |||
// unimplemented for guest user avatars | |||
} | |||
/** | |||
* Removing avatars isn't implemented for guests. | |||
*/ | |||
public function remove() { | |||
public function remove(bool $silent = false): void { | |||
// unimplemented for guest user avatars | |||
} | |||
/** | |||
* Generates an avatar for the guest. | |||
* | |||
* @param int $size The desired image size. | |||
* @return InMemoryFile | |||
*/ | |||
public function getFile($size) { | |||
public function getFile(int $size): ISimpleFile { | |||
$avatar = $this->generateAvatar($this->userDisplayName, $size); | |||
return new InMemoryFile('avatar.png', $avatar); | |||
} | |||
@@ -103,9 +94,8 @@ class GuestAvatar extends Avatar { | |||
* @param string $feature The changed feature | |||
* @param mixed $oldValue The previous value | |||
* @param mixed $newValue The new value | |||
* @return void | |||
*/ | |||
public function userChanged($feature, $oldValue, $newValue) { | |||
public function userChanged(string $feature, $oldValue, $newValue): void { | |||
if ($feature === 'displayName') { | |||
$this->userDisplayName = $newValue; | |||
} | |||
@@ -113,8 +103,6 @@ class GuestAvatar extends Avatar { | |||
/** | |||
* Guests don't have custom avatars. | |||
* | |||
* @return bool | |||
*/ | |||
public function isCustomAvatar(): bool { | |||
return false; |
@@ -44,11 +44,8 @@ use Psr\Log\LoggerInterface; | |||
* for faster retrieval, unlike the GuestAvatar. | |||
*/ | |||
class PlaceholderAvatar extends Avatar { | |||
/** @var ISimpleFolder */ | |||
private $folder; | |||
/** @var User */ | |||
private $user; | |||
private ISimpleFolder $folder; | |||
private User $user; | |||
/** | |||
* UserAvatar constructor. | |||
@@ -71,10 +68,8 @@ class PlaceholderAvatar extends Avatar { | |||
/** | |||
* Check if an avatar exists for the user | |||
* | |||
* @return bool | |||
*/ | |||
public function exists() { | |||
public function exists(): bool { | |||
return true; | |||
} | |||
@@ -87,14 +82,14 @@ class PlaceholderAvatar extends Avatar { | |||
* @throws NotSquareException if the image is not square | |||
* @return void | |||
*/ | |||
public function set($data) { | |||
public function set($data): void { | |||
// unimplemented for placeholder avatars | |||
} | |||
/** | |||
* Removes the users avatar. | |||
*/ | |||
public function remove(bool $silent = false) { | |||
public function remove(bool $silent = false): void { | |||
$avatars = $this->folder->getDirectoryListing(); | |||
foreach ($avatars as $avatar) { | |||
@@ -113,9 +108,7 @@ class PlaceholderAvatar extends Avatar { | |||
* @throws \OCP\Files\NotPermittedException | |||
* @throws \OCP\PreConditionNotMetException | |||
*/ | |||
public function getFile($size) { | |||
$size = (int) $size; | |||
public function getFile(int $size): ISimpleFile { | |||
$ext = 'png'; | |||
if ($size === -1) { | |||
@@ -149,8 +142,6 @@ class PlaceholderAvatar extends Avatar { | |||
/** | |||
* Returns the user display name. | |||
* | |||
* @return string | |||
*/ | |||
public function getDisplayName(): string { | |||
return $this->user->getDisplayName(); | |||
@@ -165,14 +156,12 @@ class PlaceholderAvatar extends Avatar { | |||
* @throws NotPermittedException | |||
* @throws \OCP\PreConditionNotMetException | |||
*/ | |||
public function userChanged($feature, $oldValue, $newValue) { | |||
public function userChanged(string $feature, $oldValue, $newValue): void { | |||
$this->remove(); | |||
} | |||
/** | |||
* Check if the avatar of a user is a custom uploaded one | |||
* | |||
* @return bool | |||
*/ | |||
public function isCustomAvatar(): bool { | |||
return false; |
@@ -44,17 +44,10 @@ use Psr\Log\LoggerInterface; | |||
* This class represents a registered user's avatar. | |||
*/ | |||
class UserAvatar extends Avatar { | |||
/** @var IConfig */ | |||
private $config; | |||
/** @var ISimpleFolder */ | |||
private $folder; | |||
/** @var IL10N */ | |||
private $l; | |||
/** @var User */ | |||
private $user; | |||
private IConfig $config; | |||
private ISimpleFolder $folder; | |||
private IL10N $l; | |||
private User $user; | |||
/** | |||
* UserAvatar constructor. | |||
@@ -68,7 +61,7 @@ class UserAvatar extends Avatar { | |||
public function __construct( | |||
ISimpleFolder $folder, | |||
IL10N $l, | |||
$user, | |||
User $user, | |||
LoggerInterface $logger, | |||
IConfig $config) { | |||
parent::__construct($logger); | |||
@@ -80,10 +73,8 @@ class UserAvatar extends Avatar { | |||
/** | |||
* Check if an avatar exists for the user | |||
* | |||
* @return bool | |||
*/ | |||
public function exists() { | |||
public function exists(): bool { | |||
return $this->folder->fileExists('avatar.jpg') || $this->folder->fileExists('avatar.png'); | |||
} | |||
@@ -96,7 +87,7 @@ class UserAvatar extends Avatar { | |||
* @throws NotSquareException if the image is not square | |||
* @return void | |||
*/ | |||
public function set($data) { | |||
public function set($data): void { | |||
$img = $this->getAvatarImage($data); | |||
$data = $img->data(); | |||
@@ -124,7 +115,7 @@ class UserAvatar extends Avatar { | |||
* @param IImage|resource|string|\GdImage $data An image object, imagedata or path to the avatar | |||
* @return IImage | |||
*/ | |||
private function getAvatarImage($data) { | |||
private function getAvatarImage($data): IImage { | |||
if ($data instanceof IImage) { | |||
return $data; | |||
} | |||
@@ -156,11 +147,8 @@ class UserAvatar extends Avatar { | |||
/** | |||
* Returns the avatar image type. | |||
* | |||
* @param IImage $avatar | |||
* @return string | |||
*/ | |||
private function getAvatarImageType(IImage $avatar) { | |||
private function getAvatarImageType(IImage $avatar): string { | |||
$type = substr($avatar->mimeType(), -3); | |||
if ($type === 'peg') { | |||
$type = 'jpg'; | |||
@@ -179,7 +167,7 @@ class UserAvatar extends Avatar { | |||
* @throws \Exception if the provided image is not valid | |||
* @throws NotSquareException if the image is not square | |||
*/ | |||
private function validateAvatar(IImage $avatar) { | |||
private function validateAvatar(IImage $avatar): void { | |||
$type = $this->getAvatarImageType($avatar); | |||
if ($type !== 'jpg' && $type !== 'png') { | |||
@@ -197,11 +185,10 @@ class UserAvatar extends Avatar { | |||
/** | |||
* Removes the users avatar. | |||
* @return void | |||
* @throws \OCP\Files\NotPermittedException | |||
* @throws \OCP\PreConditionNotMetException | |||
*/ | |||
public function remove(bool $silent = false) { | |||
public function remove(bool $silent = false): void { | |||
$avatars = $this->folder->getDirectoryListing(); | |||
$this->config->setUserValue($this->user->getUID(), 'avatar', 'version', | |||
@@ -219,10 +206,9 @@ class UserAvatar extends Avatar { | |||
/** | |||
* Get the extension of the avatar. If there is no avatar throw Exception | |||
* | |||
* @return string | |||
* @throws NotFoundException | |||
*/ | |||
private function getExtension() { | |||
private function getExtension(): string { | |||
if ($this->folder->fileExists('avatar.jpg')) { | |||
return 'jpg'; | |||
} elseif ($this->folder->fileExists('avatar.png')) { | |||
@@ -242,9 +228,7 @@ class UserAvatar extends Avatar { | |||
* @throws \OCP\Files\NotPermittedException | |||
* @throws \OCP\PreConditionNotMetException | |||
*/ | |||
public function getFile($size) { | |||
$size = (int) $size; | |||
public function getFile(int $size): ISimpleFile { | |||
try { | |||
$ext = $this->getExtension(); | |||
} catch (NotFoundException $e) { | |||
@@ -304,8 +288,6 @@ class UserAvatar extends Avatar { | |||
/** | |||
* Returns the user display name. | |||
* | |||
* @return string | |||
*/ | |||
public function getDisplayName(): string { | |||
return $this->user->getDisplayName(); | |||
@@ -320,7 +302,7 @@ class UserAvatar extends Avatar { | |||
* @throws NotPermittedException | |||
* @throws \OCP\PreConditionNotMetException | |||
*/ | |||
public function userChanged($feature, $oldValue, $newValue) { | |||
public function userChanged(string $feature, $oldValue, $newValue): void { | |||
// If the avatar is not generated (so an uploaded image) we skip this | |||
if (!$this->folder->fileExists('generated')) { | |||
return; | |||
@@ -331,8 +313,6 @@ class UserAvatar extends Avatar { | |||
/** | |||
* Check if the avatar of a user is a custom uploaded one | |||
* | |||
* @return bool | |||
*/ | |||
public function isCustomAvatar(): bool { | |||
return $this->config->getUserValue($this->user->getUID(), 'avatar', 'generated', 'false') !== 'true'; |
@@ -151,6 +151,8 @@ use OC\SystemTag\ManagerFactory as SystemTagManagerFactory; | |||
use OC\Tagging\TagMapper; | |||
use OC\Talk\Broker; | |||
use OC\Template\JSCombiner; | |||
use OC\User\Listeners\UserChangedListener; | |||
use OC\User\Listeners\UserDeletedListener; | |||
use OCA\Theming\ImageManager; | |||
use OCA\Theming\ThemingDefaults; | |||
use OCA\Theming\Util; | |||
@@ -180,7 +182,6 @@ use OCP\Files\IMimeTypeLoader; | |||
use OCP\Files\IRootFolder; | |||
use OCP\Files\Lock\ILockManager; | |||
use OCP\Files\Mount\IMountManager; | |||
use OCP\Files\NotFoundException; | |||
use OCP\Files\Storage\IStorageFactory; | |||
use OCP\Files\Template\ITemplateManager; | |||
use OCP\FullTextSearch\IFullTextSearchManager; | |||
@@ -217,7 +218,6 @@ use OCP\ISession; | |||
use OCP\ITagManager; | |||
use OCP\ITempManager; | |||
use OCP\IURLGenerator; | |||
use OCP\IUser; | |||
use OCP\IUserManager; | |||
use OCP\IUserSession; | |||
use OCP\L10N\IFactory; | |||
@@ -249,6 +249,7 @@ use OCP\User\Events\BeforeUserLoggedOutEvent; | |||
use OCP\User\Events\PasswordUpdatedEvent; | |||
use OCP\User\Events\PostLoginEvent; | |||
use OCP\User\Events\UserChangedEvent; | |||
use OCP\User\Events\UserDeletedEvent; | |||
use OCP\User\Events\UserLoggedInEvent; | |||
use OCP\User\Events\UserLoggedInWithCookieEvent; | |||
use OCP\User\Events\UserLoggedOutEvent; | |||
@@ -1473,52 +1474,13 @@ class Server extends ServerContainer implements IServerContainer { | |||
return $this->get(\OC\Calendar\Room\Manager::class); | |||
} | |||
private function connectDispatcher() { | |||
$dispatcher = $this->get(SymfonyAdapter::class); | |||
// Delete avatar on user deletion | |||
$dispatcher->addListener('OCP\IUser::preDelete', function (GenericEvent $e) { | |||
$logger = $this->get(LoggerInterface::class); | |||
$manager = $this->getAvatarManager(); | |||
/** @var IUser $user */ | |||
$user = $e->getSubject(); | |||
try { | |||
$avatar = $manager->getAvatar($user->getUID()); | |||
$avatar->remove(); | |||
} catch (NotFoundException $e) { | |||
// no avatar to remove | |||
} catch (\Exception $e) { | |||
// Ignore exceptions | |||
$logger->info('Could not cleanup avatar of ' . $user->getUID()); | |||
} | |||
}); | |||
$dispatcher->addListener('OCP\IUser::changeUser', function (GenericEvent $e) { | |||
$manager = $this->getAvatarManager(); | |||
/** @var IUser $user */ | |||
$user = $e->getSubject(); | |||
$feature = $e->getArgument('feature'); | |||
$oldValue = $e->getArgument('oldValue'); | |||
$value = $e->getArgument('value'); | |||
// We only change the avatar on display name changes | |||
if ($feature !== 'displayName') { | |||
return; | |||
} | |||
try { | |||
$avatar = $manager->getAvatar($user->getUID()); | |||
$avatar->userChanged($feature, $oldValue, $value); | |||
} catch (NotFoundException $e) { | |||
// no avatar to remove | |||
} | |||
}); | |||
/** @var IEventDispatcher $eventDispatched */ | |||
$eventDispatched = $this->get(IEventDispatcher::class); | |||
$eventDispatched->addServiceListener(LoginFailed::class, LoginFailedListener::class); | |||
$eventDispatched->addServiceListener(PostLoginEvent::class, UserLoggedInListener::class); | |||
private function connectDispatcher(): void { | |||
/** @var IEventDispatcher $eventDispatcher */ | |||
$eventDispatcher = $this->get(IEventDispatcher::class); | |||
$eventDispatcher->addServiceListener(LoginFailed::class, LoginFailedListener::class); | |||
$eventDispatcher->addServiceListener(PostLoginEvent::class, UserLoggedInListener::class); | |||
$eventDispatcher->addServiceListener(UserChangedEvent::class, UserChangedListener::class); | |||
$eventDispatcher->addServiceListener(UserDeletedEvent::class, UserDeletedListener::class); | |||
} | |||
/** |
@@ -0,0 +1,62 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2022 Carl Schwan <carl@carlschwan.eu> | |||
* | |||
* @license AGPL-3.0-or-later | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
namespace OC\User\Listeners; | |||
use OCP\EventDispatcher\Event; | |||
use OCP\EventDispatcher\IEventListener; | |||
use OCP\User\Events\UserChangedEvent; | |||
use OCP\Files\NotFoundException; | |||
use OCP\IAvatarManager; | |||
/** | |||
* @template-implements IEventListener<\OCP\User\Events\UserChangedEvent> | |||
*/ | |||
class UserChangedListener implements IEventListener { | |||
private IAvatarManager $avatarManager; | |||
public function __construct(IAvatarManager $avatarManager) { | |||
$this->avatarManager = $avatarManager; | |||
} | |||
public function handle(Event $event): void { | |||
if (!($event instanceof UserChangedEvent)) { | |||
return; | |||
} | |||
$user = $event->getUser(); | |||
$feature = $event->getFeature(); | |||
$oldValue = $event->getOldValue(); | |||
$value = $event->getValue(); | |||
// We only change the avatar on display name changes | |||
if ($feature === 'displayName') { | |||
try { | |||
$avatar = $this->avatarManager->getAvatar($user->getUID()); | |||
$avatar->userChanged($feature, $oldValue, $value); | |||
} catch (NotFoundException $e) { | |||
// no avatar to remove | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,63 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2022 Carl Schwan <carl@carlschwan.eu> | |||
* | |||
* @license AGPL-3.0-or-later | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
namespace OC\User\Listeners; | |||
use OCP\EventDispatcher\Event; | |||
use OCP\EventDispatcher\IEventListener; | |||
use OCP\User\Events\UserDeletedEvent; | |||
use OCP\Files\NotFoundException; | |||
use OCP\IAvatarManager; | |||
use Psr\Log\LoggerInterface; | |||
/** | |||
* @template-implements IEventListener<\OCP\User\Events\UserDeletedEvent> | |||
*/ | |||
class UserDeletedListener implements IEventListener { | |||
private IAvatarManager $avatarManager; | |||
private LoggerInterface $logger; | |||
public function __construct(LoggerInterface $logger, IAvatarManager $avatarManager) { | |||
$this->avatarManager = $avatarManager; | |||
$this->logger = $logger; | |||
} | |||
public function handle(Event $event): void { | |||
if (!($event instanceof UserDeletedEvent)) { | |||
return; | |||
} | |||
$user = $event->getUser(); | |||
// Delete avatar on user deletion | |||
try { | |||
$avatar = $this->avatarManager->getAvatar($user->getUID()); | |||
$avatar->remove(); | |||
} catch (NotFoundException $e) { | |||
// no avatar to remove | |||
} catch (\Exception $e) { | |||
// Ignore exceptions | |||
$this->logger->info('Could not cleanup avatar of ' . $user->getUID()); | |||
} | |||
} | |||
} |
@@ -36,66 +36,71 @@ use OCP\Files\SimpleFS\ISimpleFile; | |||
interface IAvatar { | |||
/** | |||
* get the users avatar | |||
* Get the users avatar | |||
* | |||
* @param int $size size in px of the avatar, avatars are square, defaults to 64, -1 can be used to not scale the image | |||
* @return false|\OCP\IImage containing the avatar or false if there's no image | |||
* @since 6.0.0 - size of -1 was added in 9.0.0 | |||
*/ | |||
public function get($size = 64); | |||
public function get(int $size = 64); | |||
/** | |||
* Check if an avatar exists for the user | |||
* | |||
* @return bool | |||
* @since 8.1.0 | |||
*/ | |||
public function exists(); | |||
public function exists(): bool; | |||
/** | |||
* Check if the avatar of a user is a custom uploaded one | |||
* | |||
* @return bool | |||
* @since 14.0.0 | |||
*/ | |||
public function isCustomAvatar(): bool; | |||
/** | |||
* sets the users avatar | |||
* Sets the users avatar | |||
* | |||
* @param \OCP\IImage|resource|string $data An image object, imagedata or path to set a new avatar | |||
* @throws \Exception if the provided file is not a jpg or png image | |||
* @throws \Exception if the provided image is not valid | |||
* @throws \OC\NotSquareException if the image is not square | |||
* @return void | |||
* @since 6.0.0 | |||
*/ | |||
public function set($data); | |||
public function set($data): void; | |||
/** | |||
* remove the users avatar | |||
* @return void | |||
* Remove the user's avatar | |||
* | |||
* @param bool $silent Whether removing the avatar should trigger a change | |||
* @since 6.0.0 | |||
*/ | |||
public function remove(); | |||
public function remove(bool $silent = false): void; | |||
/** | |||
* Get the file of the avatar | |||
* @param int $size -1 can be used to not scale the image | |||
* @return ISimpleFile | |||
* | |||
* @param int $size The desired image size. -1 can be used to not scale the image | |||
* @throws NotFoundException | |||
* @since 9.0.0 | |||
*/ | |||
public function getFile($size); | |||
public function getFile(int $size): ISimpleFile; | |||
/** | |||
* @param string $text | |||
* Get the avatar background color | |||
* | |||
* @return Color Object containting r g b int in the range [0, 255] | |||
* @since 14.0.0 | |||
*/ | |||
public function avatarBackgroundColor(string $text); | |||
/** | |||
* Handle a changed user | |||
* Updates the display name if changed. | |||
* | |||
* @param string $feature The changed feature | |||
* @param mixed $oldValue The previous value | |||
* @param mixed $newValue The new value | |||
* @since 13.0.0 | |||
*/ | |||
public function userChanged($feature, $oldValue, $newValue); | |||
public function userChanged(string $feature, $oldValue, $newValue): void; | |||
} |
@@ -32,16 +32,10 @@ use OCP\IUser; | |||
* @since 18.0.0 | |||
*/ | |||
class UserChangedEvent extends Event { | |||
/** @var IUser */ | |||
private $user; | |||
/** @var string */ | |||
private $feature; | |||
private IUser $user; | |||
private string $feature; | |||
/** @var mixed */ | |||
private $value; | |||
/** @var mixed */ | |||
private $oldValue; | |||
@@ -101,7 +101,7 @@ class AvatarManagerTest extends \Test\TestCase { | |||
} | |||
public function testGetAvatarForSelf() { | |||
$user = $this->createMock(IUser::class); | |||
$user = $this->createMock(\OC\User\User::class); | |||
$user | |||
->expects($this->any()) | |||
->method('getUID') | |||
@@ -151,7 +151,7 @@ class AvatarManagerTest extends \Test\TestCase { | |||
} | |||
public function testGetAvatarValidUserDifferentCasing() { | |||
$user = $this->createMock(IUser::class); | |||
$user = $this->createMock(\OC\User\User::class); | |||
$this->userManager->expects($this->once()) | |||
->method('get') | |||
->with('vaLid-USER') | |||
@@ -225,7 +225,7 @@ class AvatarManagerTest extends \Test\TestCase { | |||
->method('getUser') | |||
->willReturn($requestingUser); | |||
$user = $this->createMock(IUser::class); | |||
$user = $this->createMock(\OC\User\User::class); | |||
$user | |||
->expects($this->once()) | |||
->method('getUID') |
@@ -57,7 +57,7 @@ class UserAvatarTest extends \Test\TestCase { | |||
->willReturn($file); | |||
$this->folder->method('getFile') | |||
->willReturnCallback(function ($path) { | |||
->willReturnCallback(function (string $path) { | |||
if ($path === 'avatar.64.png') { | |||
throw new NotFoundException(); | |||
} | |||
@@ -96,7 +96,7 @@ class UserAvatarTest extends \Test\TestCase { | |||
$expected = new \OC_Image(); | |||
$expected->loadFromFile(\OC::$SERVERROOT . '/tests/data/testavatar.png'); | |||
$file = $this->createMock(File::class); | |||
$file = $this->createMock(ISimpleFile::class); | |||
$file->method('getContent')->willReturn($expected->data()); | |||
$this->folder->method('getFile')->with('avatar.128.jpg')->willReturn($file); | |||
@@ -112,7 +112,7 @@ class UserAvatarTest extends \Test\TestCase { | |||
$expected = new \OC_Image(); | |||
$expected->loadFromFile(\OC::$SERVERROOT . '/tests/data/testavatar.png'); | |||
$file = $this->createMock(File::class); | |||
$file = $this->createMock(ISimpleFile::class); | |||
$file->method('getContent')->willReturn($expected->data()); | |||
$this->folder->method('getFile')->with('avatar.jpg')->willReturn($file); | |||
@@ -132,7 +132,7 @@ class UserAvatarTest extends \Test\TestCase { | |||
$expected2->loadFromFile(\OC::$SERVERROOT . '/tests/data/testavatar.png'); | |||
$expected2->resize(32); | |||
$file = $this->createMock(File::class); | |||
$file = $this->createMock(ISimpleFile::class); | |||
$file->method('getContent')->willReturn($expected->data()); | |||
$this->folder->method('getFile') | |||
@@ -146,7 +146,7 @@ class UserAvatarTest extends \Test\TestCase { | |||
} | |||
); | |||
$newFile = $this->createMock(File::class); | |||
$newFile = $this->createMock(ISimpleFile::class); | |||
$newFile->expects($this->once()) | |||
->method('putContent') | |||
->with($expected2->data()); |