Avatar.php 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. <?php
  2. // This file is part of GNU social - https://www.gnu.org/software/social
  3. //
  4. // GNU social is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // GNU social is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  16. defined('GNUSOCIAL') || die();
  17. /**
  18. * Table Definition for avatar
  19. */
  20. class Avatar extends Managed_DataObject
  21. {
  22. public $__table = 'avatar'; // table name
  23. public $profile_id; // int(4) primary_key not_null
  24. public $original; // bool default_false
  25. public $width; // int(4) primary_key not_null
  26. public $height; // int(4) primary_key not_null
  27. public $mediatype; // varchar(32) not_null
  28. public $filename; // varchar(191) not 255 because utf8mb4 takes more space
  29. public $created; // datetime() not_null default_0000-00-00%2000%3A00%3A00
  30. public $modified; // datetime() not_null default_CURRENT_TIMESTAMP
  31. public static function schemaDef()
  32. {
  33. return array(
  34. 'fields' => array(
  35. 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'),
  36. 'original' => array('type' => 'bool', 'default' => false, 'description' => 'uploaded by user or generated?'),
  37. 'width' => array('type' => 'int', 'not null' => true, 'description' => 'image width'),
  38. 'height' => array('type' => 'int', 'not null' => true, 'description' => 'image height'),
  39. 'mediatype' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'file type'),
  40. 'filename' => array('type' => 'varchar', 'length' => 191, 'description' => 'local filename, if local'),
  41. 'created' => array('type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'),
  42. 'modified' => array('type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'),
  43. ),
  44. 'primary key' => array('profile_id', 'width', 'height'),
  45. 'unique keys' => array(
  46. // 'avatar_filename_key' => array('filename'),
  47. ),
  48. 'foreign keys' => array(
  49. 'avatar_profile_id_fkey' => array('profile', array('profile_id' => 'id')),
  50. ),
  51. 'indexes' => array(
  52. 'avatar_profile_id_idx' => array('profile_id'),
  53. ),
  54. );
  55. }
  56. // We clean up the file, too
  57. public function delete($useWhere = false)
  58. {
  59. $filename = $this->filename;
  60. if (file_exists(Avatar::path($filename))) {
  61. @unlink(Avatar::path($filename));
  62. }
  63. return parent::delete($useWhere);
  64. }
  65. /*
  66. * Deletes all avatars (but may spare the original) from a profile.
  67. *
  68. * @param Profile $target The profile we're deleting avatars of.
  69. * @param boolean $original Whether original should be removed or not.
  70. */
  71. public static function deleteFromProfile(Profile $target, $original = true)
  72. {
  73. try {
  74. $avatars = self::getProfileAvatars($target);
  75. foreach ($avatars as $avatar) {
  76. if ($avatar->original && !$original) {
  77. continue;
  78. }
  79. $avatar->delete();
  80. }
  81. } catch (NoAvatarException $e) {
  82. // There are no avatars to delete, a sort of success.
  83. }
  84. return true;
  85. }
  86. protected static $_avatars = [];
  87. /*
  88. * Get an avatar by profile. Currently can't call newSize with $height
  89. */
  90. public static function byProfile(Profile $target, $width=null, $height=null)
  91. {
  92. $width = intval($width);
  93. $height = !is_null($height) ? intval($height) : null;
  94. if (is_null($height)) {
  95. $height = $width;
  96. }
  97. $size = "{$width}x{$height}";
  98. if (!isset(self::$_avatars[$target->id])) {
  99. self::$_avatars[$target->id] = array();
  100. } elseif (isset(self::$_avatars[$target->id][$size])) {
  101. return self::$_avatars[$target->id][$size];
  102. }
  103. $avatar = null;
  104. if (Event::handle('StartProfileGetAvatar', array($target, $width, &$avatar))) {
  105. $avatar = self::pkeyGet(
  106. array(
  107. 'profile_id' => $target->id,
  108. 'width' => $width,
  109. 'height' => $height,
  110. )
  111. );
  112. Event::handle('EndProfileGetAvatar', array($target, $width, &$avatar));
  113. }
  114. if (is_null($avatar)) {
  115. // Obviously we can't find an avatar, so let's resize the original!
  116. $avatar = Avatar::newSize($target, $width);
  117. } elseif (!($avatar instanceof Avatar)) {
  118. throw new NoAvatarException($target, $avatar);
  119. }
  120. self::$_avatars[$target->id]["{$avatar->width}x{$avatar->height}"] = $avatar;
  121. return $avatar;
  122. }
  123. public static function getUploaded(Profile $target)
  124. {
  125. $avatar = new Avatar();
  126. $avatar->profile_id = $target->id;
  127. $avatar->original = true;
  128. if (!$avatar->find(true)) {
  129. throw new NoAvatarException($target, $avatar);
  130. }
  131. if (!file_exists(Avatar::path($avatar->filename))) {
  132. // The delete call may be odd for, say, unmounted filesystems
  133. // that cause a file to currently not exist, but actually it does...
  134. $avatar->delete();
  135. throw new NoAvatarException($target, $avatar);
  136. }
  137. return $avatar;
  138. }
  139. public static function getProfileAvatars(Profile $target)
  140. {
  141. $avatar = new Avatar();
  142. $avatar->profile_id = $target->id;
  143. if (!$avatar->find()) {
  144. throw new NoAvatarException($target, $avatar);
  145. }
  146. return $avatar->fetchAll();
  147. }
  148. /**
  149. * Where should the avatar go for this user?
  150. */
  151. public static function filename($id, $extension, $size = null, $extra = null)
  152. {
  153. if ($size) {
  154. return $id . '-' . $size . (($extra) ? ('-' . $extra) : '') . $extension;
  155. } else {
  156. return $id . '-original' . (($extra) ? ('-' . $extra) : '') . $extension;
  157. }
  158. }
  159. public static function path($filename)
  160. {
  161. $dir = common_config('avatar', 'dir');
  162. if ($dir[strlen($dir)-1] != '/') {
  163. $dir .= '/';
  164. }
  165. return $dir . $filename;
  166. }
  167. public static function url($filename)
  168. {
  169. $path = common_config('avatar', 'url_base');
  170. if ($path[strlen($path)-1] != '/') {
  171. $path .= '/';
  172. }
  173. if ($path[0] != '/') {
  174. $path = '/'.$path;
  175. }
  176. $server = common_config('avatar', 'server');
  177. if (empty($server)) {
  178. $server = common_config('site', 'server');
  179. }
  180. $ssl = (common_config('avatar', 'ssl') || GNUsocial::useHTTPS());
  181. $protocol = ($ssl) ? 'https' : 'http';
  182. return $protocol.'://'.$server.$path.$filename;
  183. }
  184. public function displayUrl()
  185. {
  186. return Avatar::url($this->filename);
  187. }
  188. public static function urlByProfile(Profile $target, $width = null, $height = null)
  189. {
  190. try {
  191. return self::byProfile($target, $width, $height)->displayUrl();
  192. } catch (Exception $e) {
  193. return self::defaultImage($width);
  194. }
  195. }
  196. public static function defaultImage($size = null)
  197. {
  198. if (is_null($size)) {
  199. $size = AVATAR_PROFILE_SIZE;
  200. }
  201. static $sizenames = array(AVATAR_PROFILE_SIZE => 'profile',
  202. AVATAR_STREAM_SIZE => 'stream',
  203. AVATAR_MINI_SIZE => 'mini');
  204. return Theme::path('default-avatar-'.$sizenames[$size].'.png');
  205. }
  206. public static function newSize(Profile $target, $width)
  207. {
  208. $width = intval($width);
  209. if ($width < 1 || $width > common_config('avatar', 'maxsize')) {
  210. // TRANS: An error message when avatar size is unreasonable
  211. throw new Exception(_m('Avatar size too large'));
  212. }
  213. // So far we only have square avatars and I don't have time to
  214. // rewrite support for non-square ones right now ;)
  215. $height = $width;
  216. $original = Avatar::getUploaded($target);
  217. $imagefile = new ImageFile(null, Avatar::path($original->filename));
  218. $filename = Avatar::filename(
  219. $target->getID(),
  220. image_type_to_extension($imagefile->preferredType()),
  221. $width,
  222. common_timestamp()
  223. );
  224. $imagefile->resizeTo(Avatar::path($filename), array('width'=>$width, 'height'=>$height));
  225. $scaled = clone($original);
  226. $scaled->original = false;
  227. $scaled->width = $width;
  228. $scaled->height = $height;
  229. $scaled->filename = $filename;
  230. $scaled->created = common_sql_now();
  231. if (!$scaled->insert()) {
  232. // TRANS: An error message when unable to insert avatar data into the db
  233. throw new Exception(_m('Could not insert new avatar data to database'));
  234. }
  235. // Return the new avatar object
  236. return $scaled;
  237. }
  238. }