Skip to content

Commit d0a1741

Browse files
authored
Store the path to the user’s avatar (#6443)
* Avatars do not require thumbnails * Store the path to the user’s avatar in the user table This denormalization step allows us to show the user’s avatar without joining other tables or fetching additional data on runtime. * Notify file processors when an image is replaced with the WebP variant * Do not store the avatar in the user storage * Use the denormalized value to fetch avatars
1 parent 7cefdc5 commit d0a1741

16 files changed

+130
-108
lines changed

wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.2_step1.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
use wcf\system\database\table\column\IntDatabaseTableColumn;
1212
use wcf\system\database\table\column\MediumtextDatabaseTableColumn;
13+
use wcf\system\database\table\column\VarcharDatabaseTableColumn;
1314
use wcf\system\database\table\index\DatabaseTableForeignKey;
1415
use wcf\system\database\table\index\DatabaseTableIndex;
1516
use wcf\system\database\table\PartialDatabaseTable;
@@ -26,6 +27,8 @@
2627
IntDatabaseTableColumn::create('avatarFileID')
2728
->length(10)
2829
->defaultValue(null),
30+
VarcharDatabaseTableColumn::create('avatarPathname')
31+
->defaultValue(null),
2932
IntDatabaseTableColumn::create('coverPhotoFileID')
3033
->length(10)
3134
->defaultValue(null),

wcfsetup/install/files/lib/data/user/TUserAvatarObjectList.class.php

Lines changed: 0 additions & 36 deletions
This file was deleted.

wcfsetup/install/files/lib/data/user/User.class.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
* @property-read string $registrationIpAddress ip address of the user at the time of registration or empty if user has been created manually or if no ip address are logged
5050
* @property-read int|null $avatarID id of the user's avatar or null if they have no avatar
5151
* @property-read int|null $avatarFileID id of the user's avatar core file or null if they have no avatar
52+
* @property-read string|null $avatarPathname pathname of the user's avatar relative to the core itself
5253
* @property-read int $disableAvatar is `1` if the user's avatar has been disabled, otherwise `0`
5354
* @property-read string $disableAvatarReason reason why the user's avatar is disabled
5455
* @property-read int $disableAvatarExpires timestamp at which the user's avatar will automatically be enabled again

wcfsetup/install/files/lib/data/user/UserProfile.class.php

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use wcf\data\user\avatar\AvatarDecorator;
1111
use wcf\data\user\avatar\DefaultAvatar;
1212
use wcf\data\user\avatar\IUserAvatar;
13+
use wcf\data\user\avatar\StaticAvatar;
1314
use wcf\data\user\cover\photo\DefaultUserCoverPhoto;
1415
use wcf\data\user\cover\photo\IUserCoverPhoto;
1516
use wcf\data\user\cover\photo\UserCoverPhoto;
@@ -353,19 +354,8 @@ public function getAvatar()
353354
$avatar = null;
354355
if (!$this->disableAvatar) {
355356
if ($this->canSeeAvatar()) {
356-
if ($this->avatarFileID !== null) {
357-
$data = UserStorageHandler::getInstance()->getField('avatar', $this->userID);
358-
if ($data === null) {
359-
$avatar = FileRuntimeCache::getInstance()->getObject($this->avatarFileID);
360-
361-
UserStorageHandler::getInstance()->update(
362-
$this->userID,
363-
'avatar',
364-
\serialize($avatar)
365-
);
366-
} else {
367-
$avatar = \unserialize($data);
368-
}
357+
if ($this->avatarPathname !== null) {
358+
$avatar = new StaticAvatar($this->avatarPathname);
369359
} else {
370360
$parameters = ['avatar' => null];
371361
EventHandler::getInstance()->fireAction($this, 'getAvatar', $parameters);

wcfsetup/install/files/lib/data/user/UserProfileList.class.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
class UserProfileList extends UserList
1717
{
18-
use TUserAvatarObjectList;
19-
2018
/**
2119
* @inheritDoc
2220
*/
@@ -56,8 +54,6 @@ public function readObjects()
5654

5755
parent::readObjects();
5856

59-
$this->cacheAvatarFiles();
60-
6157
$coverPhotoFileIDs = [];
6258
foreach ($this->objects as $object) {
6359
if ($object->coverPhotoFileID) {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace wcf\data\user\avatar;
4+
5+
use wcf\system\file\processor\UserAvatarFileProcessor;
6+
use wcf\system\WCF;
7+
use wcf\util\StringUtil;
8+
9+
/**
10+
* Constructs an avatar from a static filepath.
11+
*
12+
* @author Alexander Ebert
13+
* @copyright 2001-2025 WoltLab GmbH
14+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
15+
* @since 6.2
16+
*/
17+
final class StaticAvatar implements IUserAvatar, ISafeFormatAvatar
18+
{
19+
private readonly string $src;
20+
21+
public function __construct(string $pathname)
22+
{
23+
$this->src = WCF::getPath() . $pathname;
24+
}
25+
26+
#[\Override]
27+
public function getImageTag($size = null)
28+
{
29+
if ($size === null) {
30+
$size = UserAvatarFileProcessor::AVATAR_SIZE;
31+
}
32+
33+
return '<img src="' . StringUtil::encodeHTML($this->getURL($size)) . '" width="' . $size . '" height="' . $size . '" alt="" class="userAvatarImage">';
34+
}
35+
36+
#[\Override]
37+
public function getSafeURL(?int $size = null): string
38+
{
39+
return $this->getURL($size);
40+
}
41+
42+
#[\Override]
43+
public function getSafeImageTag(?int $size = null): string
44+
{
45+
return '<img src="' . StringUtil::encodeHTML($this->getSafeURL($size)) . '" width="' . $size . '" height="' . $size . '" alt="" class="userAvatarImage">';
46+
}
47+
48+
#[\Override]
49+
public function getURL($size = null)
50+
{
51+
return $this->src;
52+
}
53+
54+
#[\Override]
55+
public function getHeight()
56+
{
57+
return UserAvatarFileProcessor::AVATAR_SIZE;
58+
}
59+
60+
#[\Override]
61+
public function getWidth()
62+
{
63+
return UserAvatarFileProcessor::AVATAR_SIZE;
64+
}
65+
}

wcfsetup/install/files/lib/data/user/follow/UserFollowerList.class.php

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace wcf\data\user\follow;
44

5-
use wcf\data\user\TUserAvatarObjectList;
65
use wcf\data\user\User;
76
use wcf\data\user\UserProfile;
87

@@ -21,8 +20,6 @@
2120
*/
2221
class UserFollowerList extends UserFollowList
2322
{
24-
use TUserAvatarObjectList;
25-
2623
/**
2724
* @inheritDoc
2825
*/
@@ -50,18 +47,10 @@ public function __construct()
5047
{
5148
parent::__construct();
5249

53-
$this->sqlSelects .= "user_table.username, user_table.email, user_table.disableAvatar";
50+
$this->sqlSelects .= "user_table.username, user_table.email, user_table.disableAvatar, user_table.avatarPathname";
5451

5552
$this->sqlJoins .= "
5653
LEFT JOIN wcf1_user user_table
5754
ON user_table.userID = user_follow.userID";
5855
}
59-
60-
#[\Override]
61-
public function readObjects()
62-
{
63-
parent::readObjects();
64-
65-
$this->cacheAvatarFiles();
66-
}
6756
}

wcfsetup/install/files/lib/data/user/ignore/ViewableUserIgnoreList.class.php

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace wcf\data\user\ignore;
44

5-
use wcf\data\user\TUserAvatarObjectList;
65
use wcf\data\user\User;
76
use wcf\data\user\UserProfile;
87

@@ -15,8 +14,6 @@
1514
*/
1615
class ViewableUserIgnoreList extends UserIgnoreList
1716
{
18-
use TUserAvatarObjectList;
19-
2017
/**
2118
* @inheritDoc
2219
*/
@@ -58,12 +55,4 @@ public function __construct()
5855

5956
$this->sqlSelects .= ", user_table.*";
6057
}
61-
62-
#[\Override]
63-
public function readObjects()
64-
{
65-
parent::readObjects();
66-
67-
$this->cacheAvatarFiles();
68-
}
6958
}

wcfsetup/install/files/lib/data/user/online/UsersOnlineList.class.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use wcf\data\option\OptionAction;
66
use wcf\data\session\SessionList;
77
use wcf\data\user\group\UserGroup;
8-
use wcf\data\user\TUserAvatarObjectList;
98
use wcf\data\user\User;
109
use wcf\data\user\UserProfile;
1110
use wcf\system\event\EventHandler;
@@ -24,8 +23,6 @@
2423
*/
2524
class UsersOnlineList extends SessionList
2625
{
27-
use TUserAvatarObjectList;
28-
2926
/**
3027
* @inheritDoc
3128
*/
@@ -91,8 +88,6 @@ public function readObjects()
9188
}
9289
$this->objectIDs = $this->indexToObject;
9390
$this->rewind();
94-
95-
$this->cacheAvatarFiles();
9691
}
9792

9893
/**

wcfsetup/install/files/lib/data/user/profile/visitor/UserProfileVisitorList.class.php

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace wcf\data\user\profile\visitor;
44

55
use wcf\data\DatabaseObjectList;
6-
use wcf\data\user\TUserAvatarObjectList;
76
use wcf\data\user\User;
87
use wcf\data\user\UserProfile;
98

@@ -18,8 +17,6 @@
1817
*/
1918
class UserProfileVisitorList extends DatabaseObjectList
2019
{
21-
use TUserAvatarObjectList;
22-
2320
/**
2421
* @inheritDoc
2522
*/
@@ -42,18 +39,10 @@ public function __construct()
4239
{
4340
parent::__construct();
4441

45-
$this->sqlSelects .= "user_table.username, user_table.email, user_table.disableAvatar";
42+
$this->sqlSelects .= "user_table.username, user_table.email, user_table.disableAvatar, user_table.avatarPathname";
4643

4744
$this->sqlJoins .= "
4845
LEFT JOIN wcf1_user user_table
4946
ON user_table.userID = user_profile_visitor.userID";
5047
}
51-
52-
#[\Override]
53-
public function readObjects()
54-
{
55-
parent::readObjects();
56-
57-
$this->cacheAvatarFiles();
58-
}
5948
}

0 commit comments

Comments
 (0)