Avatar.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. <?php
  2. declare(strict_types = 1);
  3. // {{{ License
  4. // This file is part of GNU social - https://www.gnu.org/software/social
  5. //
  6. // GNU social is free software: you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // GNU social is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  18. // }}}
  19. namespace Component\Avatar;
  20. use App\Core\Cache;
  21. use App\Core\DB\DB;
  22. use App\Core\Event;
  23. use App\Core\GSFile;
  24. use App\Core\Modules\Component;
  25. use App\Core\Router\RouteLoader;
  26. use App\Core\Router\Router;
  27. use App\Util\Common;
  28. use Component\Attachment\Entity\Attachment;
  29. use Component\Attachment\Entity\AttachmentThumbnail;
  30. use Component\Avatar\Controller as C;
  31. use Component\Avatar\Exception\NoAvatarException;
  32. use Symfony\Component\HttpFoundation\Request;
  33. class Avatar extends Component
  34. {
  35. public function onInitializeComponent()
  36. {
  37. }
  38. public function onAddRoute(RouteLoader $r): bool
  39. {
  40. $r->connect('avatar_actor', '/actor/{actor_id<\d+>}/avatar/{size<full|big|medium|small>?medium}', [Controller\Avatar::class, 'avatar_view']);
  41. $r->connect('avatar_default', '/avatar/default/{size<full|big|medium|small>?medium}', [Controller\Avatar::class, 'default_avatar_view']);
  42. $r->connect('avatar_settings', '/settings/avatar', [Controller\Avatar::class, 'settings_avatar']);
  43. return Event::next;
  44. }
  45. /**
  46. * @throws \App\Util\Exception\ClientException
  47. */
  48. public function onPopulateSettingsTabs(Request $request, string $section, &$tabs): bool
  49. {
  50. if ($section === 'profile') {
  51. $tabs[] = [
  52. 'title' => 'Avatar',
  53. 'desc' => 'Change your avatar.',
  54. 'id' => 'settings-avatar',
  55. 'controller' => C\Avatar::settings_avatar($request),
  56. ];
  57. }
  58. return Event::next;
  59. }
  60. public function onAvatarUpdate(int $actor_id): bool
  61. {
  62. Cache::delete("avatar-{$actor_id}");
  63. foreach (['full', 'big', 'medium', 'small'] as $size) {
  64. foreach ([Router::ABSOLUTE_PATH, Router::ABSOLUTE_URL] as $type) {
  65. Cache::delete("avatar-url-{$actor_id}-{$size}-{$type}");
  66. }
  67. Cache::delete("avatar-file-info-{$actor_id}-{$size}");
  68. }
  69. return Event::next;
  70. }
  71. // UTILS ----------------------------------
  72. /**
  73. * Get the avatar associated with the given Actor id
  74. */
  75. public static function getAvatar(?int $actor_id = null): Entity\Avatar
  76. {
  77. $actor_id = $actor_id ?: Common::userId();
  78. return GSFile::error(
  79. NoAvatarException::class,
  80. $actor_id,
  81. Cache::get(
  82. "avatar-{$actor_id}",
  83. function () use ($actor_id) {
  84. return DB::dql(
  85. 'select a from Component\Avatar\Entity\Avatar a '
  86. . 'where a.actor_id = :actor_id',
  87. ['actor_id' => $actor_id],
  88. );
  89. },
  90. ),
  91. );
  92. }
  93. /**
  94. * Get the cached avatar associated with the given Actor id, or the current user if not given
  95. */
  96. public static function getUrl(int $actor_id, string $size = 'medium', int $type = Router::ABSOLUTE_PATH): string
  97. {
  98. try {
  99. return self::getAvatar($actor_id)->getUrl($size, $type);
  100. } catch (NoAvatarException) {
  101. return Router::url('avatar_default', ['size' => $size], $type);
  102. }
  103. }
  104. public static function getDimensions(int $actor_id, string $size = 'medium')
  105. {
  106. try {
  107. $attachment = self::getAvatar($actor_id)->getAttachment();
  108. return ['width' => (int) $attachment->getWidth(), 'height' => (int) $attachment->getHeight()];
  109. } catch (NoAvatarException) {
  110. return ['width' => (int) (Common::config('thumbnail', 'small')), 'height' => (int) (Common::config('thumbnail', 'small'))];
  111. }
  112. }
  113. /**
  114. * Get the cached avatar file info associated with the given Actor id
  115. *
  116. * Returns the avatar file's hash, mimetype, title and path.
  117. * Ensures exactly one cached value exists
  118. */
  119. public static function getAvatarFileInfo(int $actor_id, string $size = 'medium'): array
  120. {
  121. $res = Cache::get(
  122. "avatar-file-info-{$actor_id}-{$size}",
  123. function () use ($actor_id) {
  124. return DB::dql(
  125. 'select f.id, f.filename, a.title, f.mimetype '
  126. . 'from Component\Attachment\Entity\Attachment f '
  127. . 'join Component\Avatar\Entity\Avatar a with f.id = a.attachment_id '
  128. . 'where a.actor_id = :actor_id',
  129. ['actor_id' => $actor_id],
  130. );
  131. },
  132. );
  133. if ($res === []) { // Avatar not found
  134. $filepath = INSTALLDIR . '/public/assets/default-avatar.svg';
  135. return [
  136. 'id' => null,
  137. 'filepath' => $filepath,
  138. 'mimetype' => 'image/svg+xml',
  139. 'filename' => null,
  140. 'title' => 'default_avatar.svg',
  141. ];
  142. } else {
  143. $res = $res[0]; // A user must always only have one avatar.
  144. if ($size === 'full') {
  145. $res['filepath'] = Attachment::getByPK(['id' => $res['id']])->getPath();
  146. } else {
  147. $res['filepath'] = AttachmentThumbnail::getOrCreate(Attachment::getByPK(['id' => $res['id']]), $size)->getPath();
  148. }
  149. return $res;
  150. }
  151. }
  152. }