Status.php 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. <?php
  2. namespace App;
  3. use Auth;
  4. use Illuminate\Database\Eloquent\Model;
  5. use Illuminate\Database\Eloquent\SoftDeletes;
  6. use Storage;
  7. class Status extends Model
  8. {
  9. use SoftDeletes;
  10. /**
  11. * The attributes that should be mutated to dates.
  12. *
  13. * @var array
  14. */
  15. protected $dates = ['deleted_at'];
  16. protected $fillable = ['profile_id', 'visibility', 'in_reply_to_id'];
  17. public function profile()
  18. {
  19. return $this->belongsTo(Profile::class);
  20. }
  21. public function media()
  22. {
  23. return $this->hasMany(Media::class);
  24. }
  25. public function firstMedia()
  26. {
  27. return $this->hasMany(Media::class)->orderBy('order', 'asc')->first();
  28. }
  29. public function viewType()
  30. {
  31. $media = $this->firstMedia();
  32. $mime = explode('/', $media->mime)[0];
  33. $count = $this->media()->count();
  34. $type = ($mime == 'image') ? 'image' : 'video';
  35. if($count > 1) {
  36. $type = ($type == 'image') ? 'album' : 'video-album';
  37. }
  38. return $type;
  39. }
  40. public function thumb($showNsfw = false)
  41. {
  42. $type = $this->viewType();
  43. $is_nsfw = !$showNsfw ? $this->is_nsfw : false;
  44. if ($this->media->count() == 0 || $is_nsfw || !in_array($type,['image', 'album', 'video'])) {
  45. return 'data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==';
  46. }
  47. return url(Storage::url($this->firstMedia()->thumbnail_path));
  48. }
  49. public function url()
  50. {
  51. $id = $this->id;
  52. $username = $this->profile->username;
  53. $path = config('app.url')."/p/{$username}/{$id}";
  54. return url($path);
  55. }
  56. public function permalink($suffix = '/activity')
  57. {
  58. $id = $this->id;
  59. $username = $this->profile->username;
  60. $path = config('app.url')."/p/{$username}/{$id}{$suffix}";
  61. return url($path);
  62. }
  63. public function editUrl()
  64. {
  65. return $this->url().'/edit';
  66. }
  67. public function mediaUrl()
  68. {
  69. $media = $this->firstMedia();
  70. $path = $media->media_path;
  71. $hash = is_null($media->processed_at) ? md5('unprocessed') : md5($media->created_at);
  72. $url = Storage::url($path)."?v={$hash}";
  73. return url($url);
  74. }
  75. public function likes()
  76. {
  77. return $this->hasMany(Like::class);
  78. }
  79. public function liked() : bool
  80. {
  81. if(Auth::check() == false) {
  82. return false;
  83. }
  84. $profile = Auth::user()->profile;
  85. return Like::whereProfileId($profile->id)->whereStatusId($this->id)->count();
  86. }
  87. public function comments()
  88. {
  89. return $this->hasMany(self::class, 'in_reply_to_id');
  90. }
  91. public function bookmarked()
  92. {
  93. if (!Auth::check()) {
  94. return false;
  95. }
  96. $profile = Auth::user()->profile;
  97. return Bookmark::whereProfileId($profile->id)->whereStatusId($this->id)->count();
  98. }
  99. public function shares()
  100. {
  101. return $this->hasMany(self::class, 'reblog_of_id');
  102. }
  103. public function shared() : bool
  104. {
  105. if(Auth::check() == false) {
  106. return false;
  107. }
  108. $profile = Auth::user()->profile;
  109. return self::whereProfileId($profile->id)->whereReblogOfId($this->id)->count();
  110. }
  111. public function parent()
  112. {
  113. $parent = $this->in_reply_to_id ?? $this->reblog_of_id;
  114. if (!empty($parent)) {
  115. return $this->findOrFail($parent);
  116. }
  117. }
  118. public function conversation()
  119. {
  120. return $this->hasOne(Conversation::class);
  121. }
  122. public function hashtags()
  123. {
  124. return $this->hasManyThrough(
  125. Hashtag::class,
  126. StatusHashtag::class,
  127. 'status_id',
  128. 'id',
  129. 'id',
  130. 'hashtag_id'
  131. );
  132. }
  133. public function mentions()
  134. {
  135. return $this->hasManyThrough(
  136. Profile::class,
  137. Mention::class,
  138. 'status_id',
  139. 'id',
  140. 'id',
  141. 'profile_id'
  142. );
  143. }
  144. public function reportUrl()
  145. {
  146. return route('report.form')."?type=post&id={$this->id}";
  147. }
  148. public function toActivityStream()
  149. {
  150. $media = $this->media;
  151. $mediaCollection = [];
  152. foreach ($media as $image) {
  153. $mediaCollection[] = [
  154. 'type' => 'Link',
  155. 'href' => $image->url(),
  156. 'mediaType' => $image->mime,
  157. ];
  158. }
  159. $obj = [
  160. '@context' => 'https://www.w3.org/ns/activitystreams',
  161. 'type' => 'Image',
  162. 'name' => null,
  163. 'url' => $mediaCollection,
  164. ];
  165. return $obj;
  166. }
  167. public function replyToText()
  168. {
  169. $actorName = $this->profile->username;
  170. return "{$actorName} ".__('notification.commented');
  171. }
  172. public function replyToHtml()
  173. {
  174. $actorName = $this->profile->username;
  175. $actorUrl = $this->profile->url();
  176. return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> ".
  177. __('notification.commented');
  178. }
  179. public function recentComments()
  180. {
  181. return $this->comments()->orderBy('created_at', 'desc')->take(3);
  182. }
  183. public function toActivityPubObject()
  184. {
  185. if($this->local == false) {
  186. return;
  187. }
  188. $profile = $this->profile;
  189. $to = $this->scopeToAudience('to');
  190. $cc = $this->scopeToAudience('cc');
  191. return [
  192. '@context' => 'https://www.w3.org/ns/activitystreams',
  193. 'id' => $this->permalink(),
  194. 'type' => 'Create',
  195. 'actor' => $profile->permalink(),
  196. 'published' => $this->created_at->format('c'),
  197. 'to' => $to,
  198. 'cc' => $cc,
  199. 'object' => [
  200. 'id' => $this->url(),
  201. 'type' => 'Note',
  202. 'summary' => null,
  203. 'inReplyTo' => null,
  204. 'published' => $this->created_at->format('c'),
  205. 'url' => $this->url(),
  206. 'attributedTo' => $this->profile->url(),
  207. 'to' => $to,
  208. 'cc' => $cc,
  209. 'sensitive' => (bool) $this->is_nsfw,
  210. 'content' => $this->rendered,
  211. 'attachment' => $this->media->map(function($media) {
  212. return [
  213. 'type' => 'Document',
  214. 'mediaType' => $media->mime,
  215. 'url' => $media->url(),
  216. 'name' => null
  217. ];
  218. })->toArray()
  219. ]
  220. ];
  221. }
  222. public function scopeToAudience($audience)
  223. {
  224. if(!in_array($audience, ['to', 'cc']) || $this->local == false) {
  225. return;
  226. }
  227. $res = [];
  228. $res['to'] = [];
  229. $res['cc'] = [];
  230. $scope = $this->scope;
  231. $mentions = $this->mentions->map(function ($mention) {
  232. return $mention->permalink();
  233. })->toArray();
  234. switch ($scope) {
  235. case 'public':
  236. $res['to'] = [
  237. "https://www.w3.org/ns/activitystreams#Public"
  238. ];
  239. $res['cc'] = array_merge([$this->profile->permalink('/followers')], $mentions);
  240. break;
  241. case 'unlisted':
  242. break;
  243. case 'private':
  244. break;
  245. case 'direct':
  246. break;
  247. }
  248. return $res[$audience];
  249. }
  250. }