diff --git a/app/Avatar.php b/app/Avatar.php index a7f8e4e67..b72510765 100644 --- a/app/Avatar.php +++ b/app/Avatar.php @@ -3,8 +3,16 @@ namespace App; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; class Avatar extends Model { + use SoftDeletes; + /** + * The attributes that should be mutated to dates. + * + * @var array + */ + protected $dates = ['deleted_at']; } diff --git a/app/Http/Controllers/CommentController.php b/app/Http/Controllers/CommentController.php index 1ec7bbf4d..8b8eded8e 100644 --- a/app/Http/Controllers/CommentController.php +++ b/app/Http/Controllers/CommentController.php @@ -34,8 +34,8 @@ class CommentController extends Controller $reply = new Status(); $reply->profile_id = $profile->id; - $reply->caption = $comment; - $reply->rendered = e($comment); + $reply->caption = e(strip_tags($comment)); + $reply->rendered = $comment; $reply->in_reply_to_id = $status->id; $reply->in_reply_to_profile_id = $status->profile_id; $reply->save(); diff --git a/app/Http/Controllers/StatusController.php b/app/Http/Controllers/StatusController.php index 4e5c2e8d4..704beea6c 100644 --- a/app/Http/Controllers/StatusController.php +++ b/app/Http/Controllers/StatusController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers; use Auth, Cache; use App\Jobs\StatusPipeline\{NewStatusPipeline, StatusDelete}; +use App\Jobs\ImageOptimizePipeline\ImageOptimize; use Illuminate\Http\Request; use App\{Media, Profile, Status, User}; use Vinkla\Hashids\Facades\Hashids; @@ -14,7 +15,7 @@ class StatusController extends Controller { $user = Profile::whereUsername($username)->firstOrFail(); $status = Status::whereProfileId($user->id) - ->withCount(['likes', 'comments']) + ->withCount(['likes', 'comments', 'media']) ->findOrFail($id); if(!$status->media_path && $status->in_reply_to_id) { return redirect($status->url()); @@ -32,36 +33,47 @@ class StatusController extends Controller $user = Auth::user(); $this->validate($request, [ - 'photo' => 'required|mimes:jpeg,png,bmp,gif|max:' . config('pixelfed.max_photo_size'), + 'photo.*' => 'required|mimes:jpeg,png,bmp,gif|max:' . config('pixelfed.max_photo_size'), 'caption' => 'string|max:' . config('pixelfed.max_caption_length'), - 'cw' => 'nullable|string' + 'cw' => 'nullable|string', + 'filter_class' => 'nullable|string', + 'filter_name' => 'nullable|string', ]); $cw = $request->filled('cw') && $request->cw == 'on' ? true : false; $monthHash = hash('sha1', date('Y') . date('m')); $userHash = hash('sha1', $user->id . (string) $user->created_at); - $storagePath = "public/m/{$monthHash}/{$userHash}"; - $path = $request->photo->store($storagePath); $profile = $user->profile; $status = new Status; $status->profile_id = $profile->id; - $status->caption = $request->caption; + $status->caption = strip_tags($request->caption); $status->is_nsfw = $cw; $status->save(); - $media = new Media; - $media->status_id = $status->id; - $media->profile_id = $profile->id; - $media->user_id = $user->id; - $media->media_path = $path; - $media->size = $request->file('photo')->getClientSize(); - $media->mime = $request->file('photo')->getClientMimeType(); - $media->save(); - NewStatusPipeline::dispatch($status, $media); + $photos = $request->file('photo'); + $order = 1; + foreach ($photos as $k => $v) { + $storagePath = "public/m/{$monthHash}/{$userHash}"; + $path = $v->store($storagePath); + $media = new Media; + $media->status_id = $status->id; + $media->profile_id = $profile->id; + $media->user_id = $user->id; + $media->media_path = $path; + $media->size = $v->getClientSize(); + $media->mime = $v->getClientMimeType(); + $media->filter_class = $request->input('filter_class'); + $media->filter_name = $request->input('filter_name'); + $media->order = $order; + $media->save(); + ImageOptimize::dispatch($media); + $order++; + } + + NewStatusPipeline::dispatch($status); - // TODO: Parse Caption // TODO: Send to subscribers return redirect($status->url()); diff --git a/app/Jobs/StatusPipeline/NewStatusPipeline.php b/app/Jobs/StatusPipeline/NewStatusPipeline.php index 01392aa47..8939dd9ad 100644 --- a/app/Jobs/StatusPipeline/NewStatusPipeline.php +++ b/app/Jobs/StatusPipeline/NewStatusPipeline.php @@ -16,17 +16,15 @@ class NewStatusPipeline implements ShouldQueue use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $status; - protected $media; /** * Create a new job instance. * * @return void */ - public function __construct(Status $status, $media = false) + public function __construct(Status $status) { $this->status = $status; - $this->media = $media; } /** @@ -37,13 +35,10 @@ class NewStatusPipeline implements ShouldQueue public function handle() { $status = $this->status; - $media = $this->media; StatusEntityLexer::dispatch($status); - StatusActivityPubDeliver::dispatch($status); - if($media) { - ImageOptimize::dispatch($media); - } + //StatusActivityPubDeliver::dispatch($status); + Cache::forever('post.' . $status->id, $status); $redis = Redis::connection(); diff --git a/app/Jobs/StatusPipeline/StatusDelete.php b/app/Jobs/StatusPipeline/StatusDelete.php index 571b41a55..30aecd7e2 100644 --- a/app/Jobs/StatusPipeline/StatusDelete.php +++ b/app/Jobs/StatusPipeline/StatusDelete.php @@ -2,7 +2,7 @@ namespace App\Jobs\StatusPipeline; -use App\{Media, StatusHashtag, Status}; +use App\{Media, Notification, StatusHashtag, Status}; use Illuminate\Bus\Queueable; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; @@ -60,6 +60,9 @@ class StatusDelete implements ShouldQueue } $status->likes()->delete(); + Notification::whereItemType('App\Status') + ->whereItemId($status->id) + ->delete(); StatusHashtag::whereStatusId($status->id)->delete(); $status->delete(); diff --git a/app/Jobs/StatusPipeline/StatusEntityLexer.php b/app/Jobs/StatusPipeline/StatusEntityLexer.php index c1d09ccb7..c9dff4d59 100644 --- a/app/Jobs/StatusPipeline/StatusEntityLexer.php +++ b/app/Jobs/StatusPipeline/StatusEntityLexer.php @@ -6,21 +6,28 @@ use Cache; use App\{ Hashtag, Media, + Mention, + Profile, Status, StatusHashtag }; use App\Util\Lexer\Hashtag as HashtagLexer; +use App\Util\Lexer\{Autolink, Extractor}; use Illuminate\Bus\Queueable; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use App\Jobs\MentionPipeline\MentionPipeline; class StatusEntityLexer implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $status; + protected $entities; + protected $autolink; + /** * Create a new job instance. * @@ -39,22 +46,40 @@ class StatusEntityLexer implements ShouldQueue public function handle() { $status = $this->status; - $this->parseHashtags(); + $this->parseEntities(); } - public function parseHashtags() + public function parseEntities() + { + $this->extractEntities(); + } + + public function extractEntities() + { + $this->entities = Extractor::create()->extract($this->status->caption); + $this->autolinkStatus(); + } + + public function autolinkStatus() + { + $this->autolink = Autolink::create()->autolink($this->status->caption); + $this->storeEntities(); + } + + public function storeEntities() { $status = $this->status; - $text = e($status->caption); - $tags = HashtagLexer::getHashtags($text); - $rendered = $text; - if(count($tags) > 0) { - $rendered = HashtagLexer::replaceHashtagsWithLinks($text); - } - $status->rendered = $rendered; + $this->storeHashtags(); + $this->storeMentions(); + $status->rendered = $this->autolink; + $status->entities = json_encode($this->entities); $status->save(); - - Cache::forever('post.' . $status->id, $status); + } + + public function storeHashtags() + { + $tags = array_unique($this->entities['hashtags']); + $status = $this->status; foreach($tags as $tag) { $slug = str_slug($tag); @@ -64,11 +89,32 @@ class StatusEntityLexer implements ShouldQueue ['slug' => $slug] ); - $stag = new StatusHashtag; - $stag->status_id = $status->id; - $stag->hashtag_id = $htag->id; - $stag->save(); + StatusHashtag::firstOrCreate( + ['status_id' => $status->id], + ['hashtag_id' => $htag->id] + ); } - } + + public function storeMentions() + { + $mentions = array_unique($this->entities['mentions']); + $status = $this->status; + + foreach($mentions as $mention) { + $mentioned = Profile::whereUsername($mention)->first(); + + if(empty($mentioned) || !isset($mentioned->id)) { + continue; + } + + $m = new Mention; + $m->status_id = $status->id; + $m->profile_id = $mentioned->id; + $m->save(); + + MentionPipeline::dispatch($status, $m); + } + } + } diff --git a/app/Like.php b/app/Like.php index a4ce9f0a8..c70b647bc 100644 --- a/app/Like.php +++ b/app/Like.php @@ -3,9 +3,19 @@ namespace App; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; class Like extends Model { + use SoftDeletes; + + /** + * The attributes that should be mutated to dates. + * + * @var array + */ + protected $dates = ['deleted_at']; + public function actor() { return $this->belongsTo(Profile::class, 'profile_id', 'id'); diff --git a/app/Media.php b/app/Media.php index 8cc2ffd1e..7c9138965 100644 --- a/app/Media.php +++ b/app/Media.php @@ -2,11 +2,21 @@ namespace App; -use Illuminate\Database\Eloquent\Model; use Storage; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; class Media extends Model { + use SoftDeletes; + + /** + * The attributes that should be mutated to dates. + * + * @var array + */ + protected $dates = ['deleted_at']; + public function url() { $path = $this->media_path; diff --git a/app/Mention.php b/app/Mention.php index 3473c2924..bc76bdc97 100644 --- a/app/Mention.php +++ b/app/Mention.php @@ -3,9 +3,18 @@ namespace App; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; class Mention extends Model { + use SoftDeletes; + + /** + * The attributes that should be mutated to dates. + * + * @var array + */ + protected $dates = ['deleted_at']; public function profile() { diff --git a/app/Notification.php b/app/Notification.php index c40e0a553..9aef5b197 100644 --- a/app/Notification.php +++ b/app/Notification.php @@ -3,28 +3,37 @@ namespace App; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; class Notification extends Model { + use SoftDeletes; - public function actor() - { - return $this->belongsTo(Profile::class, 'actor_id', 'id'); - } + /** + * The attributes that should be mutated to dates. + * + * @var array + */ + protected $dates = ['deleted_at']; + + public function actor() + { + return $this->belongsTo(Profile::class, 'actor_id', 'id'); + } - public function profile() - { - return $this->belongsTo(Profile::class, 'profile_id', 'id'); - } + public function profile() + { + return $this->belongsTo(Profile::class, 'profile_id', 'id'); + } - public function item() - { - return $this->morphTo(); - } + public function item() + { + return $this->morphTo(); + } - public function status() - { - return $this->belongsTo(Status::class, 'item_id', 'id'); - } + public function status() + { + return $this->belongsTo(Status::class, 'item_id', 'id'); + } } diff --git a/app/Profile.php b/app/Profile.php index b0bcd3da3..009ed2cbf 100644 --- a/app/Profile.php +++ b/app/Profile.php @@ -5,9 +5,18 @@ namespace App; use Storage; use App\Util\Lexer\PrettyNumber; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; class Profile extends Model { + use SoftDeletes; + + /** + * The attributes that should be mutated to dates. + * + * @var array + */ + protected $dates = ['deleted_at']; protected $hidden = [ 'private_key', ]; diff --git a/app/Status.php b/app/Status.php index dd9bf98c4..fe3fc26d2 100644 --- a/app/Status.php +++ b/app/Status.php @@ -2,12 +2,21 @@ namespace App; -use Illuminate\Database\Eloquent\Model; use Storage; -use Vinkla\Hashids\Facades\Hashids; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; class Status extends Model { + use SoftDeletes; + + /** + * The attributes that should be mutated to dates. + * + * @var array + */ + protected $dates = ['deleted_at']; + public function profile() { return $this->belongsTo(Profile::class); @@ -25,7 +34,7 @@ class Status extends Model public function thumb() { - if($this->media->count() == 0) { + if($this->media->count() == 0 || $this->is_nsfw) { return "data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=="; } return url(Storage::url($this->firstMedia()->thumbnail_path)); @@ -43,6 +52,11 @@ class Status extends Model return url($path); } + public function editUrl() + { + return $this->url() . '/edit'; + } + public function mediaUrl() { $media = $this->firstMedia(); diff --git a/app/User.php b/app/User.php index 33297e481..38edc3e9d 100644 --- a/app/User.php +++ b/app/User.php @@ -3,11 +3,19 @@ namespace App; use Illuminate\Notifications\Notifiable; +use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { - use Notifiable; + use Notifiable, SoftDeletes; + + /** + * The attributes that should be mutated to dates. + * + * @var array + */ + protected $dates = ['deleted_at']; /** * The attributes that are mass assignable. diff --git a/database/migrations/2018_06_11_030049_add_filters_to_media_table.php b/database/migrations/2018_06_11_030049_add_filters_to_media_table.php new file mode 100644 index 000000000..5a3dbf73d --- /dev/null +++ b/database/migrations/2018_06_11_030049_add_filters_to_media_table.php @@ -0,0 +1,34 @@ +string('filter_name')->nullable()->after('orientation'); + $table->string('filter_class')->nullable()->after('filter_name'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('media', function (Blueprint $table) { + $table->dropColumn('filter_name'); + $table->dropColumn('filter_class'); + }); + } +} diff --git a/database/migrations/2018_06_14_001318_add_soft_deletes_to_models.php b/database/migrations/2018_06_14_001318_add_soft_deletes_to_models.php new file mode 100644 index 000000000..a839ddf74 --- /dev/null +++ b/database/migrations/2018_06_14_001318_add_soft_deletes_to_models.php @@ -0,0 +1,58 @@ +softDeletes(); + }); + + Schema::table('likes', function ($table) { + $table->softDeletes(); + }); + + Schema::table('media', function ($table) { + $table->softDeletes(); + }); + + Schema::table('mentions', function ($table) { + $table->softDeletes(); + }); + + Schema::table('notifications', function ($table) { + $table->softDeletes(); + }); + + Schema::table('profiles', function ($table) { + $table->softDeletes(); + }); + + Schema::table('statuses', function ($table) { + $table->softDeletes(); + }); + + Schema::table('users', function ($table) { + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/public/css/app.css b/public/css/app.css index 4fc774d19..dee2a7931 100644 Binary files a/public/css/app.css and b/public/css/app.css differ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index a0fc846da..2d3e3dd2b 100644 Binary files a/public/mix-manifest.json and b/public/mix-manifest.json differ diff --git a/resources/views/status/show.blade.php b/resources/views/status/show.blade.php index 80036fdfb..8d2e27d67 100644 --- a/resources/views/status/show.blade.php +++ b/resources/views/status/show.blade.php @@ -16,7 +16,47 @@
- + @if($status->is_nsfw && $status->media_count == 1) +
+

+

NSFW / Hidden Image + + + +

+
+ @elseif(!$status->is_nsfw && $status->media_count == 1) +
+ +
+ @elseif($status->is_nsfw && $status->media_count > 1) + + @elseif(!$status->is_nsfw && $status->media_count > 1) + + @endif
@@ -40,7 +80,7 @@ @foreach($status->comments->reverse()->take(10) as $item)

{{$item->profile->username}} - {!!$item->rendered!!} {{$item->created_at->diffForHumans(null, true, true ,true)}} + {!! $item->rendered ?? e($item->caption) !!} {{$item->created_at->diffForHumans(null, true, true ,true)}}

@endforeach
diff --git a/resources/views/status/template.blade.php b/resources/views/status/template.blade.php index e43215bf8..7a3910887 100644 --- a/resources/views/status/template.blade.php +++ b/resources/views/status/template.blade.php @@ -15,6 +15,7 @@ Embed @if(Auth::check()) @if(Auth::user()->profile->id === $item->profile->id || Auth::user()->is_admin == true) + Edit
@csrf @@ -29,16 +30,16 @@
@if($item->is_nsfw) -
+

NSFW / Hidden Image - +

@else - + @endif @@ -84,7 +85,7 @@ {{$status->profile->username}} - {!!$status->rendered!!} + {!! $item->rendered ?? e($item->caption) !!} {{$status->created_at->diffForHumans(null, true, true, true)}} @@ -95,7 +96,7 @@ @foreach($item->comments->reverse()->take(3) as $comment)

{{$comment->profile->username}} - {{ str_limit($comment->caption, 125) }} + {!! str_limit($item->rendered ?? e($item->caption), 150) !!}

@endforeach @endif