Avatar.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  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;
  22. use App\Core\Event;
  23. use App\Core\GSFile;
  24. use App\Core\Modules\Component;
  25. use App\Core\Router;
  26. use App\Util\Common;
  27. use Component\Attachment\Entity\Attachment;
  28. use Component\Attachment\Entity\AttachmentThumbnail;
  29. use Component\Avatar\Controller as C;
  30. use Component\Avatar\Exception\NoAvatarException;
  31. use EventResult;
  32. use Symfony\Component\HttpFoundation\Request;
  33. class Avatar extends Component
  34. {
  35. public function onInitializeComponent(): EventResult
  36. {
  37. return EventResult::next;
  38. }
  39. public function onAddRoute(Router $r): EventResult
  40. {
  41. $r->connect('avatar_actor', '/actor/{actor_id<\d+>}/avatar/{size<full|big|medium|small>?medium}', [Controller\Avatar::class, 'avatar_view']);
  42. $r->connect('avatar_default', '/avatar/default/{size<full|big|medium|small>?medium}', [Controller\Avatar::class, 'default_avatar_view']);
  43. $r->connect('avatar_settings', '/settings/avatar', [Controller\Avatar::class, 'settings_avatar']);
  44. return Event::next;
  45. }
  46. /**
  47. * @param SettingsTabsType $tabs
  48. *
  49. * @throws \App\Util\Exception\ClientException
  50. */
  51. public function onPopulateSettingsTabs(Request $request, string $section, &$tabs): EventResult
  52. {
  53. if ($section === 'profile') {
  54. $tabs[] = [
  55. 'title' => 'Avatar',
  56. 'desc' => 'Change your avatar.',
  57. 'id' => 'settings-avatar',
  58. 'controller' => C\Avatar::settings_avatar($request),
  59. ];
  60. }
  61. return Event::next;
  62. }
  63. public function onAvatarUpdate(int $actor_id): EventResult
  64. {
  65. Cache::delete("avatar-{$actor_id}");
  66. foreach (['full', 'big', 'medium', 'small'] as $size) {
  67. foreach ([Router::ABSOLUTE_PATH, Router::ABSOLUTE_URL] as $type) {
  68. Cache::delete("avatar-url-{$actor_id}-{$size}-{$type}");
  69. }
  70. Cache::delete("avatar-file-info-{$actor_id}-{$size}");
  71. }
  72. return Event::next;
  73. }
  74. // UTILS ----------------------------------
  75. /**
  76. * Get the avatar associated with the given Actor id
  77. */
  78. public static function getAvatar(?int $actor_id = null): Entity\Avatar
  79. {
  80. $actor_id = $actor_id ?: Common::userId();
  81. return GSFile::error(
  82. NoAvatarException::class,
  83. $actor_id,
  84. Cache::get(
  85. "avatar-{$actor_id}",
  86. function () use ($actor_id) {
  87. return DB::dql(
  88. 'select a from Component\Avatar\Entity\Avatar a '
  89. . 'where a.actor_id = :actor_id',
  90. ['actor_id' => $actor_id],
  91. );
  92. },
  93. ),
  94. );
  95. }
  96. /**
  97. * Get the cached avatar associated with the given Actor id, or the current user if not given
  98. */
  99. public static function getUrl(int $actor_id, string $size = 'medium', int $type = Router::ABSOLUTE_PATH): string
  100. {
  101. try {
  102. return self::getAvatar($actor_id)->getUrl($size, $type);
  103. } catch (NoAvatarException) {
  104. return Router::url('avatar_default', ['size' => $size], $type);
  105. }
  106. }
  107. public static function getDimensions(int $actor_id, string $size = 'medium')
  108. {
  109. try {
  110. $attachment = self::getAvatar($actor_id)->getAttachment();
  111. return ['width' => (int) $attachment->getWidth(), 'height' => (int) $attachment->getHeight()];
  112. } catch (NoAvatarException) {
  113. return ['width' => (int) (Common::config('thumbnail', 'small')), 'height' => (int) (Common::config('thumbnail', 'small'))];
  114. }
  115. }
  116. /**
  117. * Get the cached avatar file info associated with the given Actor id
  118. *
  119. * Returns the avatar file's hash, mimetype, title and path.
  120. * Ensures exactly one cached value exists
  121. *
  122. * @return array{id: null|int, filename: null|string, title: string, mimetype: string, filepath?: string}
  123. */
  124. public static function getAvatarFileInfo(int $actor_id, string $size = 'medium'): array
  125. {
  126. $res = Cache::get(
  127. "avatar-file-info-{$actor_id}-{$size}",
  128. function () use ($actor_id) {
  129. return DB::dql(
  130. 'select f.id, f.filename, a.title, f.mimetype '
  131. . 'from Component\Attachment\Entity\Attachment f '
  132. . 'join Component\Avatar\Entity\Avatar a with f.id = a.attachment_id '
  133. . 'where a.actor_id = :actor_id',
  134. ['actor_id' => $actor_id],
  135. );
  136. },
  137. );
  138. if ($res === []) { // Avatar not found
  139. $filepath = INSTALLDIR . '/public/assets/default-avatar.svg';
  140. return [
  141. 'id' => null,
  142. 'filepath' => $filepath,
  143. 'mimetype' => 'image/svg+xml',
  144. 'filename' => null,
  145. 'title' => 'default_avatar.svg',
  146. ];
  147. } else {
  148. $res = $res[0]; // A user must always only have one avatar.
  149. if ($size === 'full') {
  150. $res['filepath'] = Attachment::getByPK(['id' => $res['id']])->getPath();
  151. } else {
  152. $res['filepath'] = AttachmentThumbnail::getOrCreate(Attachment::getByPK(['id' => $res['id']]), $size)->getPath();
  153. }
  154. return $res;
  155. }
  156. }
  157. }