Apply fixes from StyleCI

This commit is contained in:
daniel 2018-08-28 03:07:36 +00:00 committed by StyleCI Bot
parent 75de27f482
commit b8abbdd90f
261 changed files with 3368 additions and 3231 deletions

View file

@ -8,11 +8,11 @@ class Comment extends Model
{ {
public function profile() public function profile()
{ {
return $this->belongsTo(Profile::class); return $this->belongsTo(Profile::class);
} }
public function status() public function status()
{ {
return $this->belongsTo(Status::class); return $this->belongsTo(Status::class);
} }
} }

View file

@ -2,9 +2,9 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Media;
use App\Jobs\ImageOptimizePipeline\ImageOptimize; use App\Jobs\ImageOptimizePipeline\ImageOptimize;
use App\Media;
use Illuminate\Console\Command;
class CatchUnoptimizedMedia extends Command class CatchUnoptimizedMedia extends Command
{ {
@ -40,7 +40,7 @@ class CatchUnoptimizedMedia extends Command
public function handle() public function handle()
{ {
$medias = Media::whereNull('processed_at')->take(50)->get(); $medias = Media::whereNull('processed_at')->take(50)->get();
foreach($medias as $media) { foreach ($medias as $media) {
ImageOptimize::dispatch($media); ImageOptimize::dispatch($media);
} }
} }

View file

@ -2,9 +2,10 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\{Follower, Profile}; use App\Follower;
use Illuminate\Console\Command;
use App\Jobs\FollowPipeline\FollowPipeline; use App\Jobs\FollowPipeline\FollowPipeline;
use App\Profile;
use Illuminate\Console\Command;
class SeedFollows extends Command class SeedFollows extends Command
{ {
@ -41,12 +42,12 @@ class SeedFollows extends Command
{ {
$limit = 10000; $limit = 10000;
for ($i=0; $i < $limit; $i++) { for ($i = 0; $i < $limit; $i++) {
try { try {
$actor = Profile::inRandomOrder()->firstOrFail(); $actor = Profile::inRandomOrder()->firstOrFail();
$target = Profile::inRandomOrder()->firstOrFail(); $target = Profile::inRandomOrder()->firstOrFail();
$follow = new Follower; $follow = new Follower();
$follow->profile_id = $actor->id; $follow->profile_id = $actor->id;
$follow->following_id = $target->id; $follow->following_id = $target->id;
$follow->save(); $follow->save();

View file

@ -19,7 +19,8 @@ class Kernel extends ConsoleKernel
/** /**
* Define the application's command schedule. * Define the application's command schedule.
* *
* @param \Illuminate\Console\Scheduling\Schedule $schedule * @param \Illuminate\Console\Scheduling\Schedule $schedule
*
* @return void * @return void
*/ */
protected function schedule(Schedule $schedule) protected function schedule(Schedule $schedule)

View file

@ -8,8 +8,9 @@ class EmailVerification extends Model
{ {
public function url() public function url()
{ {
$base = config('app.url'); $base = config('app.url');
$path = '/i/confirm-email/' . $this->user_token . '/' . $this->random_token; $path = '/i/confirm-email/'.$this->user_token.'/'.$this->random_token;
return "{$base}{$path}";
return "{$base}{$path}";
} }
} }

View file

@ -2,14 +2,11 @@
namespace App\Events; namespace App\Events;
use Illuminate\Broadcasting\Channel; use App\User;
use Illuminate\Queue\SerializesModels; use App\UserSetting;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Foundation\Events\Dispatchable;
use App\{User, UserSetting}; use Illuminate\Queue\SerializesModels;
class AuthLoginEvent class AuthLoginEvent
{ {
@ -27,8 +24,8 @@ class AuthLoginEvent
public function handle(User $user) public function handle(User $user)
{ {
if(empty($user->settings)) { if (empty($user->settings)) {
$settings = new UserSetting; $settings = new UserSetting();
$settings->user_id = $user->id; $settings->user_id = $user->id;
$settings->save(); $settings->save();
} }

View file

@ -29,7 +29,8 @@ class Handler extends ExceptionHandler
/** /**
* Report or log an exception. * Report or log an exception.
* *
* @param \Exception $exception * @param \Exception $exception
*
* @return void * @return void
*/ */
public function report(Exception $exception) public function report(Exception $exception)
@ -40,8 +41,9 @@ class Handler extends ExceptionHandler
/** /**
* Render an exception into an HTTP response. * Render an exception into an HTTP response.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Exception $exception * @param \Exception $exception
*
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function render($request, Exception $exception) public function render($request, Exception $exception)

View file

@ -8,30 +8,32 @@ class Follower extends Model
{ {
public function actor() public function actor()
{ {
return $this->belongsTo(Profile::class, 'profile_id', 'id'); return $this->belongsTo(Profile::class, 'profile_id', 'id');
} }
public function target() public function target()
{ {
return $this->belongsTo(Profile::class, 'following_id', 'id'); return $this->belongsTo(Profile::class, 'following_id', 'id');
} }
public function profile() public function profile()
{ {
return $this->belongsTo(Profile::class, 'following_id', 'id'); return $this->belongsTo(Profile::class, 'following_id', 'id');
} }
public function toText() public function toText()
{ {
$actorName = $this->actor->username; $actorName = $this->actor->username;
return "{$actorName} " . __('notification.startedFollowingYou');
return "{$actorName} ".__('notification.startedFollowingYou');
} }
public function toHtml() public function toHtml()
{ {
$actorName = $this->actor->username; $actorName = $this->actor->username;
$actorUrl = $this->actor->url(); $actorUrl = $this->actor->url();
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> " .
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> ".
__('notification.startedFollowingYou'); __('notification.startedFollowingYou');
} }
} }

View file

@ -6,11 +6,11 @@ use Illuminate\Database\Eloquent\Model;
class Hashtag extends Model class Hashtag extends Model
{ {
public $fillable = ['name','slug']; public $fillable = ['name', 'slug'];
public function posts() public function posts()
{ {
return $this->hasManyThrough( return $this->hasManyThrough(
Status::class, Status::class,
StatusHashtag::class, StatusHashtag::class,
'hashtag_id', 'hashtag_id',
@ -22,7 +22,6 @@ class Hashtag extends Model
public function url() public function url()
{ {
return config('routes.hashtag.base') . $this->slug; return config('routes.hashtag.base').$this->slug;
} }
} }

View file

@ -2,77 +2,78 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request; use App\EmailVerification;
use Carbon\Carbon;
use App\Mail\ConfirmEmail; use App\Mail\ConfirmEmail;
use Auth, DB, Cache, Mail, Redis; use App\Notification;
use App\{ use App\Profile;
EmailVerification, use App\User;
Notification, use App\UserFilter;
Profile, use Auth;
User, use Cache;
UserFilter use Carbon\Carbon;
}; use Illuminate\Http\Request;
use Mail;
use Redis;
class AccountController extends Controller class AccountController extends Controller
{ {
protected $filters = [ protected $filters = [
'user.mute', 'user.mute',
'user.block' 'user.block',
]; ];
public function __construct() public function __construct()
{ {
$this->middleware('auth'); $this->middleware('auth');
} }
public function notifications(Request $request) public function notifications(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
'page' => 'nullable|min:1|max:3', 'page' => 'nullable|min:1|max:3',
'a' => 'nullable|alpha_dash', 'a' => 'nullable|alpha_dash',
]); ]);
$profile = Auth::user()->profile; $profile = Auth::user()->profile;
$action = $request->input('a'); $action = $request->input('a');
$timeago = Carbon::now()->subMonths(6); $timeago = Carbon::now()->subMonths(6);
if($action && in_array($action, ['comment', 'follow', 'mention'])) { if ($action && in_array($action, ['comment', 'follow', 'mention'])) {
$notifications = Notification::whereProfileId($profile->id) $notifications = Notification::whereProfileId($profile->id)
->whereAction($action) ->whereAction($action)
->whereDate('created_at', '>', $timeago) ->whereDate('created_at', '>', $timeago)
->orderBy('id','desc') ->orderBy('id', 'desc')
->simplePaginate(30); ->simplePaginate(30);
} else { } else {
$notifications = Notification::whereProfileId($profile->id) $notifications = Notification::whereProfileId($profile->id)
->whereDate('created_at', '>', $timeago) ->whereDate('created_at', '>', $timeago)
->orderBy('id','desc') ->orderBy('id', 'desc')
->simplePaginate(30); ->simplePaginate(30);
} }
return view('account.activity', compact('profile', 'notifications')); return view('account.activity', compact('profile', 'notifications'));
} }
public function followingActivity(Request $request) public function followingActivity(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
'page' => 'nullable|min:1|max:3', 'page' => 'nullable|min:1|max:3',
'a' => 'nullable|alpha_dash', 'a' => 'nullable|alpha_dash',
]); ]);
$profile = Auth::user()->profile; $profile = Auth::user()->profile;
$action = $request->input('a'); $action = $request->input('a');
$timeago = Carbon::now()->subMonths(1); $timeago = Carbon::now()->subMonths(1);
$following = $profile->following->pluck('id'); $following = $profile->following->pluck('id');
$notifications = Notification::whereIn('actor_id', $following) $notifications = Notification::whereIn('actor_id', $following)
->where('profile_id', '!=', $profile->id) ->where('profile_id', '!=', $profile->id)
->whereDate('created_at', '>', $timeago) ->whereDate('created_at', '>', $timeago)
->orderBy('notifications.id','desc') ->orderBy('notifications.id', 'desc')
->simplePaginate(30); ->simplePaginate(30);
return view('account.following', compact('profile', 'notifications')); return view('account.following', compact('profile', 'notifications'));
} }
public function verifyEmail(Request $request) public function verifyEmail(Request $request)
{ {
return view('account.verify_email'); return view('account.verify_email');
} }
public function sendVerifyEmail(Request $request) public function sendVerifyEmail(Request $request)
@ -82,19 +83,18 @@ class AccountController extends Controller
->where('created_at', '>', $timeLimit)->count(); ->where('created_at', '>', $timeLimit)->count();
$exists = EmailVerification::whereUserId(Auth::id())->count(); $exists = EmailVerification::whereUserId(Auth::id())->count();
if($recentAttempt == 1 && $exists == 1) { if ($recentAttempt == 1 && $exists == 1) {
return redirect()->back()->with('error', 'A verification email has already been sent recently. Please check your email, or try again later.'); return redirect()->back()->with('error', 'A verification email has already been sent recently. Please check your email, or try again later.');
} elseif ($recentAttempt == 0 && $exists !== 0) { } elseif ($recentAttempt == 0 && $exists !== 0) {
// Delete old verification and send new one. // Delete old verification and send new one.
EmailVerification::whereUserId(Auth::id())->delete(); EmailVerification::whereUserId(Auth::id())->delete();
} }
$user = User::whereNull('email_verified_at')->find(Auth::id()); $user = User::whereNull('email_verified_at')->find(Auth::id());
$utoken = hash('sha512', $user->id); $utoken = hash('sha512', $user->id);
$rtoken = str_random(40); $rtoken = str_random(40);
$verify = new EmailVerification; $verify = new EmailVerification();
$verify->user_id = $user->id; $verify->user_id = $user->id;
$verify->email = $user->email; $verify->email = $user->email;
$verify->user_token = $utoken; $verify->user_token = $utoken;
@ -112,55 +112,56 @@ class AccountController extends Controller
->where('random_token', $randomToken) ->where('random_token', $randomToken)
->firstOrFail(); ->firstOrFail();
if(Auth::id() === $verify->user_id) { if (Auth::id() === $verify->user_id) {
$user = User::find(Auth::id()); $user = User::find(Auth::id());
$user->email_verified_at = Carbon::now(); $user->email_verified_at = Carbon::now();
$user->save(); $user->save();
return redirect('/');
return redirect('/');
} }
} }
public function fetchNotifications($id) public function fetchNotifications($id)
{ {
$key = config('cache.prefix') . ":user.{$id}.notifications"; $key = config('cache.prefix').":user.{$id}.notifications";
$redis = Redis::connection(); $redis = Redis::connection();
$notifications = $redis->lrange($key, 0, 30); $notifications = $redis->lrange($key, 0, 30);
if(empty($notifications)) { if (empty($notifications)) {
$notifications = Notification::whereProfileId($id) $notifications = Notification::whereProfileId($id)
->orderBy('id','desc')->take(30)->get(); ->orderBy('id', 'desc')->take(30)->get();
} else { } else {
$notifications = $this->hydrateNotifications($notifications); $notifications = $this->hydrateNotifications($notifications);
} }
return $notifications; return $notifications;
} }
public function hydrateNotifications($keys) public function hydrateNotifications($keys)
{ {
$prefix = 'notification.'; $prefix = 'notification.';
$notifications = collect([]); $notifications = collect([]);
foreach($keys as $key) { foreach ($keys as $key) {
$notifications->push(Cache::get("{$prefix}{$key}")); $notifications->push(Cache::get("{$prefix}{$key}"));
} }
return $notifications;
return $notifications;
} }
public function messages() public function messages()
{ {
return view('account.messages'); return view('account.messages');
} }
public function showMessage(Request $request, $id) public function showMessage(Request $request, $id)
{ {
return view('account.message'); return view('account.message');
} }
public function mute(Request $request) public function mute(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
'type' => 'required|string', 'type' => 'required|string',
'item' => 'required|integer|min:1' 'item' => 'required|integer|min:1',
]); ]);
$user = Auth::user()->profile; $user = Auth::user()->profile;
@ -168,50 +169,49 @@ class AccountController extends Controller
$item = $request->input('item'); $item = $request->input('item');
$action = "{$type}.mute"; $action = "{$type}.mute";
if(!in_array($action, $this->filters)) { if (!in_array($action, $this->filters)) {
return abort(406); return abort(406);
} }
$filterable = []; $filterable = [];
switch ($type) { switch ($type) {
case 'user': case 'user':
$profile = Profile::findOrFail($item); $profile = Profile::findOrFail($item);
if($profile->id == $user->id) { if ($profile->id == $user->id) {
return abort(403); return abort(403);
} }
$class = get_class($profile); $class = get_class($profile);
$filterable['id'] = $profile->id; $filterable['id'] = $profile->id;
$filterable['type'] = $class; $filterable['type'] = $class;
break; break;
default: default:
# code... // code...
break; break;
} }
$filter = UserFilter::firstOrCreate([ $filter = UserFilter::firstOrCreate([
'user_id' => $user->id, 'user_id' => $user->id,
'filterable_id' => $filterable['id'], 'filterable_id' => $filterable['id'],
'filterable_type' => $filterable['type'], 'filterable_type' => $filterable['type'],
'filter_type' => 'mute' 'filter_type' => 'mute',
]); ]);
return redirect()->back(); return redirect()->back();
} }
public function block(Request $request) public function block(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
'type' => 'required|string', 'type' => 'required|string',
'item' => 'required|integer|min:1' 'item' => 'required|integer|min:1',
]); ]);
$user = Auth::user()->profile; $user = Auth::user()->profile;
$type = $request->input('type'); $type = $request->input('type');
$item = $request->input('item'); $item = $request->input('item');
$action = "{$type}.block"; $action = "{$type}.block";
if(!in_array($action, $this->filters)) { if (!in_array($action, $this->filters)) {
return abort(406); return abort(406);
} }
$filterable = []; $filterable = [];
switch ($type) { switch ($type) {
@ -221,21 +221,19 @@ class AccountController extends Controller
$filterable['id'] = $profile->id; $filterable['id'] = $profile->id;
$filterable['type'] = $class; $filterable['type'] = $class;
break; break;
default: default:
# code... // code...
break; break;
} }
$filter = UserFilter::firstOrCreate([ $filter = UserFilter::firstOrCreate([
'user_id' => $user->id, 'user_id' => $user->id,
'filterable_id' => $filterable['id'], 'filterable_id' => $filterable['id'],
'filterable_type' => $filterable['type'], 'filterable_type' => $filterable['type'],
'filter_type' => 'block' 'filter_type' => 'block',
]); ]);
return redirect()->back(); return redirect()->back();
} }
} }

View file

@ -2,84 +2,83 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use Illuminate\Http\Request; use App\Report;
use Carbon\Carbon; use Carbon\Carbon;
use App\{Comment, Like, Media, Profile, Report, Status, User}; use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
trait AdminReportController trait AdminReportController
{ {
public function updateReport(Request $request, $id) public function updateReport(Request $request, $id)
{ {
$this->validate($request, [ $this->validate($request, [
'action' => 'required|string' 'action' => 'required|string',
]); ]);
$action = $request->input('action'); $action = $request->input('action');
$actions = [ $actions = [
'ignore', 'ignore',
'cw', 'cw',
'unlist', 'unlist',
'delete', 'delete',
'shadowban', 'shadowban',
'ban' 'ban',
]; ];
if(!in_array($action, $actions)) { if (!in_array($action, $actions)) {
return abort(403); return abort(403);
} }
$report = Report::findOrFail($id); $report = Report::findOrFail($id);
$this->handleReportAction($report, $action); $this->handleReportAction($report, $action);
return response()->json(['msg'=> 'Success']); return response()->json(['msg'=> 'Success']);
} }
public function handleReportAction(Report $report, $action) public function handleReportAction(Report $report, $action)
{ {
$item = $report->reported(); $item = $report->reported();
$report->admin_seen = Carbon::now(); $report->admin_seen = Carbon::now();
switch ($action) { switch ($action) {
case 'ignore': case 'ignore':
$report->not_interested = true; $report->not_interested = true;
break; break;
case 'cw': case 'cw':
$item->is_nsfw = true; $item->is_nsfw = true;
$item->save(); $item->save();
$report->nsfw = true; $report->nsfw = true;
break; break;
case 'unlist': case 'unlist':
$item->visibility = 'unlisted'; $item->visibility = 'unlisted';
$item->save(); $item->save();
break; break;
case 'delete': case 'delete':
// Todo: fire delete job // Todo: fire delete job
$report->admin_seen = null; $report->admin_seen = null;
break; break;
case 'shadowban': case 'shadowban':
// Todo: fire delete job // Todo: fire delete job
$report->admin_seen = null; $report->admin_seen = null;
break; break;
case 'ban': case 'ban':
// Todo: fire delete job // Todo: fire delete job
$report->admin_seen = null; $report->admin_seen = null;
break; break;
default:
$report->admin_seen = null;
break;
}
$report->save(); default:
$report->admin_seen = null;
break;
}
return $this; $report->save();
return $this;
} }
} }

View file

@ -2,43 +2,48 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Media;
use App\Status;
use App\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\{Comment, Like, Media, Profile, Status, User};
class AdminController extends Controller class AdminController extends Controller
{ {
public function __construct() public function __construct()
{ {
return $this->middleware('admin'); return $this->middleware('admin');
} }
public function home() public function home()
{ {
return view('admin.home'); return view('admin.home');
} }
public function users(Request $request) public function users(Request $request)
{ {
$users = User::orderBy('id', 'desc')->paginate(10); $users = User::orderBy('id', 'desc')->paginate(10);
return view('admin.users.home', compact('users'));
}
return view('admin.users.home', compact('users'));
}
public function statuses(Request $request) public function statuses(Request $request)
{ {
$statuses = Status::orderBy('id', 'desc')->paginate(10); $statuses = Status::orderBy('id', 'desc')->paginate(10);
return view('admin.statuses.home', compact('statuses'));
return view('admin.statuses.home', compact('statuses'));
} }
public function showStatus(Request $request, $id) public function showStatus(Request $request, $id)
{ {
$status = Status::findOrFail($id); $status = Status::findOrFail($id);
return view('admin.statuses.show', compact('status'));
return view('admin.statuses.show', compact('status'));
} }
public function media(Request $request) public function media(Request $request)
{ {
$media = Status::whereHas('media')->orderby('id', 'desc')->paginate(12); $media = Status::whereHas('media')->orderby('id', 'desc')->paginate(12);
return view('admin.media.home', compact('media'));
return view('admin.media.home', compact('media'));
} }
} }

View file

@ -2,23 +2,17 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use Auth, Cache; use App\Avatar;
use App\{
Avatar,
Like,
Profile,
Status
};
use League\Fractal;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Controllers\AvatarController; use App\Http\Controllers\AvatarController;
use App\Util\Webfinger\Webfinger; use App\Http\Controllers\Controller;
use App\Transformer\Api\{
AccountTransformer,
StatusTransformer
};
use App\Jobs\AvatarPipeline\AvatarOptimize; use App\Jobs\AvatarPipeline\AvatarOptimize;
use App\Profile;
use App\Transformer\Api\AccountTransformer;
use App\Transformer\Api\StatusTransformer;
use Auth;
use Cache;
use Illuminate\Http\Request;
use League\Fractal;
use League\Fractal\Serializer\ArraySerializer; use League\Fractal\Serializer\ArraySerializer;
class BaseApiController extends Controller class BaseApiController extends Controller
@ -35,8 +29,9 @@ class BaseApiController extends Controller
public function accounts(Request $request, $id) public function accounts(Request $request, $id)
{ {
$profile = Profile::findOrFail($id); $profile = Profile::findOrFail($id);
$resource = new Fractal\Resource\Item($profile, new AccountTransformer); $resource = new Fractal\Resource\Item($profile, new AccountTransformer());
$res = $this->fractal->createData($resource)->toArray(); $res = $this->fractal->createData($resource)->toArray();
return response()->json($res, 200, [], JSON_PRETTY_PRINT); return response()->json($res, 200, [], JSON_PRETTY_PRINT);
} }
@ -44,8 +39,9 @@ class BaseApiController extends Controller
{ {
$profile = Profile::findOrFail($id); $profile = Profile::findOrFail($id);
$followers = $profile->followers; $followers = $profile->followers;
$resource = new Fractal\Resource\Collection($followers, new AccountTransformer); $resource = new Fractal\Resource\Collection($followers, new AccountTransformer());
$res = $this->fractal->createData($resource)->toArray(); $res = $this->fractal->createData($resource)->toArray();
return response()->json($res, 200, [], JSON_PRETTY_PRINT); return response()->json($res, 200, [], JSON_PRETTY_PRINT);
} }
@ -53,8 +49,9 @@ class BaseApiController extends Controller
{ {
$profile = Profile::findOrFail($id); $profile = Profile::findOrFail($id);
$following = $profile->following; $following = $profile->following;
$resource = new Fractal\Resource\Collection($following, new AccountTransformer); $resource = new Fractal\Resource\Collection($following, new AccountTransformer());
$res = $this->fractal->createData($resource)->toArray(); $res = $this->fractal->createData($resource)->toArray();
return response()->json($res, 200, [], JSON_PRETTY_PRINT); return response()->json($res, 200, [], JSON_PRETTY_PRINT);
} }
@ -62,17 +59,18 @@ class BaseApiController extends Controller
{ {
$profile = Profile::findOrFail($id); $profile = Profile::findOrFail($id);
$statuses = $profile->statuses()->orderBy('id', 'desc')->paginate(20); $statuses = $profile->statuses()->orderBy('id', 'desc')->paginate(20);
$resource = new Fractal\Resource\Collection($statuses, new StatusTransformer); $resource = new Fractal\Resource\Collection($statuses, new StatusTransformer());
$res = $this->fractal->createData($resource)->toArray(); $res = $this->fractal->createData($resource)->toArray();
return response()->json($res, 200, [], JSON_PRETTY_PRINT); return response()->json($res, 200, [], JSON_PRETTY_PRINT);
} }
public function followSuggestions(Request $request) public function followSuggestions(Request $request)
{ {
$followers = Auth::user()->profile->recommendFollowers(); $followers = Auth::user()->profile->recommendFollowers();
$resource = new Fractal\Resource\Collection($followers, new AccountTransformer); $resource = new Fractal\Resource\Collection($followers, new AccountTransformer());
$res = $this->fractal->createData($resource)->toArray(); $res = $this->fractal->createData($resource)->toArray();
return response()->json($res); return response()->json($res);
} }
@ -81,33 +79,34 @@ class BaseApiController extends Controller
$this->validate($request, [ $this->validate($request, [
'upload' => 'required|mimes:jpeg,png,gif|max:2000', 'upload' => 'required|mimes:jpeg,png,gif|max:2000',
]); ]);
try { try {
$user = Auth::user(); $user = Auth::user();
$profile = $user->profile; $profile = $user->profile;
$file = $request->file('upload'); $file = $request->file('upload');
$path = (new AvatarController())->getPath($user, $file); $path = (new AvatarController())->getPath($user, $file);
$dir = $path['root']; $dir = $path['root'];
$name = $path['name']; $name = $path['name'];
$public = $path['storage']; $public = $path['storage'];
$currentAvatar = storage_path('app/'.$profile->avatar->media_path); $currentAvatar = storage_path('app/'.$profile->avatar->media_path);
$loc = $request->file('upload')->storeAs($public, $name); $loc = $request->file('upload')->storeAs($public, $name);
$avatar = Avatar::whereProfileId($profile->id)->firstOrFail(); $avatar = Avatar::whereProfileId($profile->id)->firstOrFail();
$opath = $avatar->media_path; $opath = $avatar->media_path;
$avatar->media_path = "$public/$name"; $avatar->media_path = "$public/$name";
$avatar->thumb_path = null; $avatar->thumb_path = null;
$avatar->change_count = ++$avatar->change_count; $avatar->change_count = ++$avatar->change_count;
$avatar->last_processed_at = null; $avatar->last_processed_at = null;
$avatar->save(); $avatar->save();
Cache::forget("avatar:{$profile->id}"); Cache::forget("avatar:{$profile->id}");
AvatarOptimize::dispatch($user->profile, $currentAvatar); AvatarOptimize::dispatch($user->profile, $currentAvatar);
} catch (Exception $e) { } catch (Exception $e) {
} }
return response()->json([ return response()->json([
'code' => 200, 'code' => 200,
'msg' => 'Avatar successfully updated' 'msg' => 'Avatar successfully updated',
]); ]);
} }
} }

View file

@ -2,14 +2,14 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Auth, Cache;
use App\{Like, Status};
use Illuminate\Http\Request;
use App\Http\Controllers\Api\BaseApiController; use App\Http\Controllers\Api\BaseApiController;
use App\Like;
use Auth;
use Cache;
use Illuminate\Http\Request;
class ApiController extends BaseApiController class ApiController extends BaseApiController
{ {
public function hydrateLikes(Request $request) public function hydrateLikes(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
@ -18,7 +18,7 @@ class ApiController extends BaseApiController
]); ]);
$profile = Auth::user()->profile; $profile = Auth::user()->profile;
$res = Cache::remember('api:like-ids:user:'.$profile->id, 1440, function() use ($profile) { $res = Cache::remember('api:like-ids:user:'.$profile->id, 1440, function () use ($profile) {
return Like::whereProfileId($profile->id) return Like::whereProfileId($profile->id)
->orderBy('id', 'desc') ->orderBy('id', 'desc')
->take(1000) ->take(1000)
@ -30,6 +30,5 @@ class ApiController extends BaseApiController
public function loadMoreComments(Request $request) public function loadMoreComments(Request $request)
{ {
return;
} }
} }

View file

@ -2,8 +2,9 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\{AccountLog, User}; use App\AccountLog;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller class LoginController extends Controller
@ -41,17 +42,18 @@ class LoginController extends Controller
/** /**
* Validate the user login request. * Validate the user login request.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
*
* @return void * @return void
*/ */
public function validateLogin($request) public function validateLogin($request)
{ {
$rules = [ $rules = [
$this->username() => 'required|string', $this->username() => 'required|string',
'password' => 'required|string', 'password' => 'required|string',
]; ];
if(config('pixelfed.recaptcha')) { if (config('pixelfed.recaptcha')) {
$rules['g-recaptcha-response'] = 'required|recaptcha'; $rules['g-recaptcha-response'] = 'required|recaptcha';
} }
@ -61,13 +63,14 @@ class LoginController extends Controller
/** /**
* The user has been authenticated. * The user has been authenticated.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param mixed $user * @param mixed $user
*
* @return mixed * @return mixed
*/ */
protected function authenticated($request, $user) protected function authenticated($request, $user)
{ {
$log = new AccountLog; $log = new AccountLog();
$log->user_id = $user->id; $log->user_id = $user->id;
$log->item_id = $user->id; $log->item_id = $user->id;
$log->item_type = 'App\User'; $log->item_type = 'App\User';

View file

@ -2,12 +2,12 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\User; use App\User;
use App\Util\Lexer\RestrictedNames; use App\Util\Lexer\RestrictedNames;
use App\Http\Controllers\Controller; use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;
class RegisterController extends Controller class RegisterController extends Controller
{ {
@ -45,7 +45,8 @@ class RegisterController extends Controller
/** /**
* Get a validator for an incoming registration request. * Get a validator for an incoming registration request.
* *
* @param array $data * @param array $data
*
* @return \Illuminate\Contracts\Validation\Validator * @return \Illuminate\Contracts\Validation\Validator
*/ */
protected function validator(array $data) protected function validator(array $data)
@ -57,21 +58,21 @@ class RegisterController extends Controller
'min:2', 'min:2',
'max:15', 'max:15',
'unique:users', 'unique:users',
function($attribute, $value, $fail) { function ($attribute, $value, $fail) {
if(!ctype_alpha($value[0])) { if (!ctype_alpha($value[0])) {
return $fail($attribute . ' is invalid. Username must be alpha-numeric and start with a letter.'); return $fail($attribute.' is invalid. Username must be alpha-numeric and start with a letter.');
} }
} },
]; ];
$rules = [ $rules = [
'name' => 'required|string|max:' . config('pixelfed.max_name_length'), 'name' => 'required|string|max:'.config('pixelfed.max_name_length'),
'username' => $usernameRules, 'username' => $usernameRules,
'email' => 'required|string|email|max:255|unique:users', 'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed', 'password' => 'required|string|min:6|confirmed',
]; ];
if(config('pixelfed.recaptcha')) { if (config('pixelfed.recaptcha')) {
$rules['g-recaptcha-response'] = 'required|recaptcha'; $rules['g-recaptcha-response'] = 'required|recaptcha';
} }
@ -81,15 +82,16 @@ class RegisterController extends Controller
/** /**
* Create a new user instance after a valid registration. * Create a new user instance after a valid registration.
* *
* @param array $data * @param array $data
*
* @return \App\User * @return \App\User
*/ */
protected function create(array $data) protected function create(array $data)
{ {
return User::create([ return User::create([
'name' => $data['name'], 'name' => $data['name'],
'username' => $data['username'], 'username' => $data['username'],
'email' => $data['email'], 'email' => $data['email'],
'password' => Hash::make($data['password']), 'password' => Hash::make($data['password']),
]); ]);
} }
@ -98,7 +100,7 @@ class RegisterController extends Controller
{ {
$restricted = RestrictedNames::get(); $restricted = RestrictedNames::get();
if(in_array($username, $restricted)) { if (in_array($username, $restricted)) {
return abort(403); return abort(403);
} }
} }
@ -106,7 +108,7 @@ class RegisterController extends Controller
public function openRegistrationCheck() public function openRegistrationCheck()
{ {
$openRegistration = config('pixelfed.open_registration'); $openRegistration = config('pixelfed.open_registration');
if(false == $openRegistration) { if (false == $openRegistration) {
abort(403); abort(403);
} }
} }

View file

@ -2,10 +2,12 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Auth, Cache, Log, Storage;
use App\Avatar; use App\Avatar;
use App\Jobs\AvatarPipeline\AvatarOptimize; use App\Jobs\AvatarPipeline\AvatarOptimize;
use Auth;
use Cache;
use Illuminate\Http\Request;
use Storage;
class AvatarController extends Controller class AvatarController extends Controller
{ {
@ -17,31 +19,33 @@ class AvatarController extends Controller
public function store(Request $request) public function store(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
'avatar' => 'required|mimes:jpeg,png|max:2000' 'avatar' => 'required|mimes:jpeg,png|max:2000',
]); ]);
try { try {
$user = Auth::user(); $user = Auth::user();
$profile = $user->profile; $profile = $user->profile;
$file = $request->file('avatar'); $file = $request->file('avatar');
$path = $this->getPath($user, $file); $path = $this->getPath($user, $file);
$dir = $path['root']; $dir = $path['root'];
$name = $path['name']; $name = $path['name'];
$public = $path['storage']; $public = $path['storage'];
$currentAvatar = storage_path('app/'.$profile->avatar->media_path); $currentAvatar = storage_path('app/'.$profile->avatar->media_path);
$loc = $request->file('avatar')->storeAs($public, $name); $loc = $request->file('avatar')->storeAs($public, $name);
$avatar = Avatar::whereProfileId($profile->id)->firstOrFail(); $avatar = Avatar::whereProfileId($profile->id)->firstOrFail();
$opath = $avatar->media_path; $opath = $avatar->media_path;
$avatar->media_path = "$public/$name"; $avatar->media_path = "$public/$name";
$avatar->thumb_path = null; $avatar->thumb_path = null;
$avatar->change_count = ++$avatar->change_count; $avatar->change_count = ++$avatar->change_count;
$avatar->last_processed_at = null; $avatar->last_processed_at = null;
$avatar->save(); $avatar->save();
Cache::forget("avatar:{$profile->id}"); Cache::forget("avatar:{$profile->id}");
AvatarOptimize::dispatch($user->profile, $currentAvatar); AvatarOptimize::dispatch($user->profile, $currentAvatar);
} catch (Exception $e) { } catch (Exception $e) {
} }
return redirect()->back()->with('status', 'Avatar updated successfully. It may take a few minutes to update across the site.'); return redirect()->back()->with('status', 'Avatar updated successfully. It may take a few minutes to update across the site.');
} }
@ -54,15 +58,15 @@ class AvatarController extends Controller
$path = $this->buildPath($id); $path = $this->buildPath($id);
$dir = storage_path('app/'.$path); $dir = storage_path('app/'.$path);
$this->checkDir($dir); $this->checkDir($dir);
$name = 'avatar.' . $file->guessExtension(); $name = 'avatar.'.$file->guessExtension();
$res = ['root' => 'storage/app/' . $path, 'name' => $name, 'storage' => $path]; $res = ['root' => 'storage/app/'.$path, 'name' => $name, 'storage' => $path];
return $res; return $res;
} }
public function checkDir($path) public function checkDir($path)
{ {
if(!is_dir($path)) { if (!is_dir($path)) {
mkdir($path); mkdir($path);
} }
} }
@ -71,25 +75,26 @@ class AvatarController extends Controller
{ {
$padded = str_pad($id, 12, 0, STR_PAD_LEFT); $padded = str_pad($id, 12, 0, STR_PAD_LEFT);
$parts = str_split($padded, 3); $parts = str_split($padded, 3);
foreach($parts as $k => $part) { foreach ($parts as $k => $part) {
if($k == 0) { if ($k == 0) {
$prefix = storage_path('app/public/avatars/'.$parts[0]); $prefix = storage_path('app/public/avatars/'.$parts[0]);
$this->checkDir($prefix); $this->checkDir($prefix);
} }
if($k == 1) { if ($k == 1) {
$prefix = storage_path('app/public/avatars/'.$parts[0].'/'.$parts[1]); $prefix = storage_path('app/public/avatars/'.$parts[0].'/'.$parts[1]);
$this->checkDir($prefix); $this->checkDir($prefix);
} }
if($k == 2) { if ($k == 2) {
$prefix = storage_path('app/public/avatars/'.$parts[0].'/'.$parts[1].'/'.$parts[2]); $prefix = storage_path('app/public/avatars/'.$parts[0].'/'.$parts[1].'/'.$parts[2]);
$this->checkDir($prefix); $this->checkDir($prefix);
} }
if($k == 3) { if ($k == 3) {
$avatarpath = 'public/avatars/'.$parts[0].'/'.$parts[1].'/'.$parts[2].'/'.$parts[3]; $avatarpath = 'public/avatars/'.$parts[0].'/'.$parts[1].'/'.$parts[2].'/'.$parts[3];
$prefix = storage_path('app/'.$avatarpath); $prefix = storage_path('app/'.$avatarpath);
$this->checkDir($prefix); $this->checkDir($prefix);
} }
} }
return $avatarpath; return $avatarpath;
} }
} }

View file

@ -2,8 +2,9 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Bookmark;
use App\Status;
use Auth; use Auth;
use App\{Bookmark, Profile, Status};
use Illuminate\Http\Request; use Illuminate\Http\Request;
class BookmarkController extends Controller class BookmarkController extends Controller
@ -16,7 +17,7 @@ class BookmarkController extends Controller
public function store(Request $request) public function store(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
'item' => 'required|integer|min:1' 'item' => 'required|integer|min:1',
]); ]);
$profile = Auth::user()->profile; $profile = Auth::user()->profile;
@ -26,17 +27,16 @@ class BookmarkController extends Controller
['status_id' => $status->id], ['profile_id' => $profile->id] ['status_id' => $status->id], ['profile_id' => $profile->id]
); );
if(!$bookmark->wasRecentlyCreated) { if (!$bookmark->wasRecentlyCreated) {
$bookmark->delete(); $bookmark->delete();
} }
if($request->ajax()) { if ($request->ajax()) {
$response = ['code' => 200, 'msg' => 'Bookmark saved!']; $response = ['code' => 200, 'msg' => 'Bookmark saved!'];
} else { } else {
$response = redirect()->back(); $response = redirect()->back();
} }
return $response;
}
return $response;
}
} }

View file

@ -2,61 +2,66 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request; use App\Comment;
use App\Jobs\CommentPipeline\CommentPipeline; use App\Jobs\CommentPipeline\CommentPipeline;
use App\Jobs\StatusPipeline\NewStatusPipeline; use App\Jobs\StatusPipeline\NewStatusPipeline;
use Auth, Hashids; use App\Profile;
use App\{Comment, Profile, Status}; use App\Status;
use Auth;
use Illuminate\Http\Request;
class CommentController extends Controller class CommentController extends Controller
{ {
public function show(Request $request, $username, int $id, int $cid) public function show(Request $request, $username, int $id, int $cid)
{ {
$user = Profile::whereUsername($username)->firstOrFail(); $user = Profile::whereUsername($username)->firstOrFail();
$status = Status::whereProfileId($user->id)->whereInReplyToId($id)->findOrFail($cid); $status = Status::whereProfileId($user->id)->whereInReplyToId($id)->findOrFail($cid);
return view('status.reply', compact('user', 'status'));
return view('status.reply', compact('user', 'status'));
} }
public function showAll(Request $request, $username, int $id) public function showAll(Request $request, $username, int $id)
{ {
$user = Profile::whereUsername($username)->firstOrFail(); $user = Profile::whereUsername($username)->firstOrFail();
$status = Status::whereProfileId($user->id)->findOrFail($id); $status = Status::whereProfileId($user->id)->findOrFail($id);
$replies = Status::whereInReplyToId($id)->paginate(40); $replies = Status::whereInReplyToId($id)->paginate(40);
return view('status.comments', compact('user', 'status', 'replies'));
return view('status.comments', compact('user', 'status', 'replies'));
} }
public function store(Request $request) public function store(Request $request)
{ {
if(Auth::check() === false) { abort(403); } if (Auth::check() === false) {
$this->validate($request, [ abort(403);
}
$this->validate($request, [
'item' => 'required|integer', 'item' => 'required|integer',
'comment' => 'required|string|max:500' 'comment' => 'required|string|max:500',
]); ]);
$comment = $request->input('comment'); $comment = $request->input('comment');
$statusId = $request->item; $statusId = $request->item;
$user = Auth::user(); $user = Auth::user();
$profile = $user->profile; $profile = $user->profile;
$status = Status::findOrFail($statusId); $status = Status::findOrFail($statusId);
$reply = new Status(); $reply = new Status();
$reply->profile_id = $profile->id; $reply->profile_id = $profile->id;
$reply->caption = e($comment); $reply->caption = e($comment);
$reply->rendered = $comment; $reply->rendered = $comment;
$reply->in_reply_to_id = $status->id; $reply->in_reply_to_id = $status->id;
$reply->in_reply_to_profile_id = $status->profile_id; $reply->in_reply_to_profile_id = $status->profile_id;
$reply->save(); $reply->save();
NewStatusPipeline::dispatch($reply, false); NewStatusPipeline::dispatch($reply, false);
CommentPipeline::dispatch($status, $reply); CommentPipeline::dispatch($status, $reply);
if($request->ajax()) { if ($request->ajax()) {
$response = ['code' => 200, 'msg' => 'Comment saved', 'username' => $profile->username, 'url' => $reply->url(), 'profile' => $profile->url(), 'comment' => $reply->caption]; $response = ['code' => 200, 'msg' => 'Comment saved', 'username' => $profile->username, 'url' => $reply->url(), 'profile' => $profile->url(), 'comment' => $reply->caption];
} else { } else {
$response = redirect($status->url()); $response = redirect($status->url());
} }
return $response; return $response;
} }
} }

View file

@ -2,10 +2,10 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController class Controller extends BaseController
{ {

View file

@ -2,57 +2,60 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request; use App\Follower;
use App\{Hashtag, Follower, Profile, Status, StatusHashtag}; use App\Hashtag;
use App\Profile;
use App\Status;
use Auth; use Auth;
use Illuminate\Http\Request;
class DiscoverController extends Controller class DiscoverController extends Controller
{ {
public function __construct() public function __construct()
{ {
$this->middleware('auth'); $this->middleware('auth');
} }
public function home() public function home()
{ {
$pid = Auth::user()->profile->id; $pid = Auth::user()->profile->id;
$following = Follower::whereProfileId($pid) $following = Follower::whereProfileId($pid)
->pluck('following_id'); ->pluck('following_id');
$people = Profile::inRandomOrder() $people = Profile::inRandomOrder()
->where('id', '!=', $pid) ->where('id', '!=', $pid)
->whereNotIn('id', $following) ->whereNotIn('id', $following)
->take(3) ->take(3)
->get(); ->get();
$posts = Status::whereHas('media') $posts = Status::whereHas('media')
->where('profile_id', '!=', $pid) ->where('profile_id', '!=', $pid)
->whereNotIn('profile_id', $following) ->whereNotIn('profile_id', $following)
->orderBy('created_at', 'desc') ->orderBy('created_at', 'desc')
->simplePaginate(21); ->simplePaginate(21);
return view('discover.home', compact('people', 'posts')); return view('discover.home', compact('people', 'posts'));
} }
public function showTags(Request $request, $hashtag) public function showTags(Request $request, $hashtag)
{ {
$this->validate($request, [ $this->validate($request, [
'page' => 'nullable|integer|min:1|max:10' 'page' => 'nullable|integer|min:1|max:10',
]); ]);
$tag = Hashtag::with('posts') $tag = Hashtag::with('posts')
->withCount('posts') ->withCount('posts')
->whereSlug($hashtag) ->whereSlug($hashtag)
->firstOrFail(); ->firstOrFail();
$posts = $tag->posts() $posts = $tag->posts()
->whereIsNsfw(false) ->whereIsNsfw(false)
->whereVisibility('public') ->whereVisibility('public')
->has('media') ->has('media')
->orderBy('id','desc') ->orderBy('id', 'desc')
->simplePaginate(12); ->simplePaginate(12);
return view('discover.tags.show', compact('tag', 'posts')); return view('discover.tags.show', compact('tag', 'posts'));
} }
} }

View file

@ -2,162 +2,165 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Auth, Cache; use App\Jobs\InboxPipeline\InboxWorker;
use App\Jobs\RemoteFollowPipeline\RemoteFollowPipeline;
use App\Profile; use App\Profile;
use Carbon\Carbon; use App\Transformer\ActivityPub\ProfileOutbox;
use League\Fractal;
use Illuminate\Http\Request;
use App\Util\Lexer\Nickname; use App\Util\Lexer\Nickname;
use App\Util\Webfinger\Webfinger; use App\Util\Webfinger\Webfinger;
use App\Transformer\ActivityPub\{ use Auth;
ProfileOutbox, use Cache;
ProfileTransformer use Carbon\Carbon;
}; use Illuminate\Http\Request;
use App\Jobs\RemoteFollowPipeline\RemoteFollowPipeline; use League\Fractal;
use App\Jobs\InboxPipeline\InboxWorker;
class FederationController extends Controller class FederationController extends Controller
{ {
public function authCheck() public function authCheck()
{ {
if(!Auth::check()) { if (!Auth::check()) {
return abort(403); return abort(403);
} }
} }
public function authorizeFollow(Request $request) public function authorizeFollow(Request $request)
{ {
$this->authCheck(); $this->authCheck();
$this->validate($request, [ $this->validate($request, [
'acct' => 'required|string|min:3|max:255' 'acct' => 'required|string|min:3|max:255',
]); ]);
$acct = $request->input('acct'); $acct = $request->input('acct');
$nickname = Nickname::normalizeProfileUrl($acct); $nickname = Nickname::normalizeProfileUrl($acct);
return view('federation.authorizefollow', compact('acct', 'nickname'));
return view('federation.authorizefollow', compact('acct', 'nickname'));
} }
public function remoteFollow() public function remoteFollow()
{ {
$this->authCheck(); $this->authCheck();
return view('federation.remotefollow');
return view('federation.remotefollow');
} }
public function remoteFollowStore(Request $request) public function remoteFollowStore(Request $request)
{ {
$this->authCheck(); $this->authCheck();
$this->validate($request, [ $this->validate($request, [
'url' => 'required|string' 'url' => 'required|string',
]); ]);
if(config('pixelfed.remote_follow_enabled') !== true) { if (config('pixelfed.remote_follow_enabled') !== true) {
abort(403); abort(403);
} }
$follower = Auth::user()->profile; $follower = Auth::user()->profile;
$url = $request->input('url'); $url = $request->input('url');
RemoteFollowPipeline::dispatch($follower, $url); RemoteFollowPipeline::dispatch($follower, $url);
return redirect()->back(); return redirect()->back();
} }
public function nodeinfoWellKnown() public function nodeinfoWellKnown()
{ {
$res = [ $res = [
'links' => [ 'links' => [
[ [
'href' => config('pixelfed.nodeinfo.url'), 'href' => config('pixelfed.nodeinfo.url'),
'rel' => 'http://nodeinfo.diaspora.software/ns/schema/2.0' 'rel' => 'http://nodeinfo.diaspora.software/ns/schema/2.0',
] ],
] ],
]; ];
return response()->json($res);
return response()->json($res);
} }
public function nodeinfo() public function nodeinfo()
{ {
$res = Cache::remember('api:nodeinfo', 60, function() { $res = Cache::remember('api:nodeinfo', 60, function () {
return [ return [
'metadata' => [ 'metadata' => [
'nodeName' => config('app.name'), 'nodeName' => config('app.name'),
'software' => [ 'software' => [
'homepage' => 'https://pixelfed.org', 'homepage' => 'https://pixelfed.org',
'github' => 'https://github.com/pixelfed', 'github' => 'https://github.com/pixelfed',
'follow' => 'https://mastodon.social/@pixelfed' 'follow' => 'https://mastodon.social/@pixelfed',
], ],
], ],
'openRegistrations' => config('pixelfed.open_registration'), 'openRegistrations' => config('pixelfed.open_registration'),
'protocols' => [ 'protocols' => [
'activitypub' 'activitypub',
], ],
'services' => [ 'services' => [
'inbound' => [], 'inbound' => [],
'outbound' => [] 'outbound' => [],
], ],
'software' => [ 'software' => [
'name' => 'pixelfed', 'name' => 'pixelfed',
'version' => config('pixelfed.version') 'version' => config('pixelfed.version'),
], ],
'usage' => [ 'usage' => [
'localPosts' => \App\Status::whereLocal(true)->whereHas('media')->count(), 'localPosts' => \App\Status::whereLocal(true)->whereHas('media')->count(),
'localComments' => \App\Status::whereLocal(true)->whereNotNull('in_reply_to_id')->count(), 'localComments' => \App\Status::whereLocal(true)->whereNotNull('in_reply_to_id')->count(),
'users' => [ 'users' => [
'total' => \App\User::count(), 'total' => \App\User::count(),
'activeHalfyear' => \App\User::where('updated_at', '>', Carbon::now()->subMonths(6)->toDateTimeString())->count(), 'activeHalfyear' => \App\User::where('updated_at', '>', Carbon::now()->subMonths(6)->toDateTimeString())->count(),
'activeMonth' => \App\User::where('updated_at', '>', Carbon::now()->subMonths(1)->toDateTimeString())->count(), 'activeMonth' => \App\User::where('updated_at', '>', Carbon::now()->subMonths(1)->toDateTimeString())->count(),
] ],
], ],
'version' => '2.0' 'version' => '2.0',
]; ];
}); });
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
}
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
}
public function webfinger(Request $request) public function webfinger(Request $request)
{ {
$this->validate($request, ['resource'=>'required|string|min:3|max:255']); $this->validate($request, ['resource'=>'required|string|min:3|max:255']);
$hash = hash('sha256', $request->input('resource'));
$webfinger = Cache::remember('api:webfinger:'.$hash, 1440, function() use($request) { $hash = hash('sha256', $request->input('resource'));
$resource = $request->input('resource');
$parsed = Nickname::normalizeProfileUrl($resource); $webfinger = Cache::remember('api:webfinger:'.$hash, 1440, function () use ($request) {
$username = $parsed['username']; $resource = $request->input('resource');
$user = Profile::whereUsername($username)->firstOrFail(); $parsed = Nickname::normalizeProfileUrl($resource);
return (new Webfinger($user))->generate(); $username = $parsed['username'];
}); $user = Profile::whereUsername($username)->firstOrFail();
return response()->json($webfinger, 200, [], JSON_PRETTY_PRINT);
return (new Webfinger($user))->generate();
});
return response()->json($webfinger, 200, [], JSON_PRETTY_PRINT);
} }
public function userOutbox(Request $request, $username) public function userOutbox(Request $request, $username)
{ {
if(config('pixelfed.activitypub_enabled') == false) { if (config('pixelfed.activitypub_enabled') == false) {
abort(403); abort(403);
} }
$user = Profile::whereNull('remote_url')->whereUsername($username)->firstOrFail(); $user = Profile::whereNull('remote_url')->whereUsername($username)->firstOrFail();
$timeline = $user->statuses()->orderBy('created_at','desc')->paginate(10); $timeline = $user->statuses()->orderBy('created_at', 'desc')->paginate(10);
$fractal = new Fractal\Manager(); $fractal = new Fractal\Manager();
$resource = new Fractal\Resource\Item($user, new ProfileOutbox); $resource = new Fractal\Resource\Item($user, new ProfileOutbox());
$res = $fractal->createData($resource)->toArray(); $res = $fractal->createData($resource)->toArray();
return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
} }
public function userInbox(Request $request, $username) public function userInbox(Request $request, $username)
{ {
if(config('pixelfed.activitypub_enabled') == false) { if (config('pixelfed.activitypub_enabled') == false) {
abort(403); abort(403);
} }
$mimes = [ $mimes = [
'application/activity+json', 'application/activity+json',
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
]; ];
if(!in_array($request->header('Content-Type'), $mimes)) { if (!in_array($request->header('Content-Type'), $mimes)) {
abort(500, 'Invalid request'); abort(500, 'Invalid request');
} }
$profile = Profile::whereUsername($username)->firstOrFail(); $profile = Profile::whereUsername($username)->firstOrFail();
InboxWorker::dispatch($request, $profile, $request->all()); InboxWorker::dispatch($request, $profile, $request->all());
} }
} }

View file

@ -2,41 +2,41 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Auth; use App\Follower;
use App\{Follower, Profile};
use Illuminate\Http\Request;
use App\Jobs\FollowPipeline\FollowPipeline; use App\Jobs\FollowPipeline\FollowPipeline;
use App\Profile;
use Auth;
use Illuminate\Http\Request;
class FollowerController extends Controller class FollowerController extends Controller
{ {
public function __construct() public function __construct()
{ {
$this->middleware('auth'); $this->middleware('auth');
} }
public function store(Request $request) public function store(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
'item' => 'required|integer', 'item' => 'required|integer',
]); ]);
$user = Auth::user()->profile; $user = Auth::user()->profile;
$target = Profile::where('id', '!=', $user->id)->findOrFail($request->input('item')); $target = Profile::where('id', '!=', $user->id)->findOrFail($request->input('item'));
$isFollowing = Follower::whereProfileId($user->id)->whereFollowingId($target->id)->count(); $isFollowing = Follower::whereProfileId($user->id)->whereFollowingId($target->id)->count();
if($isFollowing == 0) { if ($isFollowing == 0) {
$follower = new Follower; $follower = new Follower();
$follower->profile_id = $user->id; $follower->profile_id = $user->id;
$follower->following_id = $target->id; $follower->following_id = $target->id;
$follower->save(); $follower->save();
FollowPipeline::dispatch($follower); FollowPipeline::dispatch($follower);
} else { } else {
$follower = Follower::whereProfileId($user->id)->whereFollowingId($target->id)->firstOrFail(); $follower = Follower::whereProfileId($user->id)->whereFollowingId($target->id)->firstOrFail();
$follower->delete(); $follower->delete();
} }
return redirect()->back();
return redirect()->back();
} }
} }

View file

@ -2,8 +2,6 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request;
class HashtagController extends Controller class HashtagController extends Controller
{ {
// //

View file

@ -2,8 +2,6 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request;
class HomeController extends Controller class HomeController extends Controller
{ {
/** /**

View file

@ -2,55 +2,58 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Auth, Cache, Hashids;
use App\{Like, Profile, Status, User};
use App\Jobs\LikePipeline\LikePipeline; use App\Jobs\LikePipeline\LikePipeline;
use App\Like;
use App\Status;
use App\User;
use Auth;
use Cache;
use Illuminate\Http\Request;
class LikeController extends Controller class LikeController extends Controller
{ {
public function __construct() public function __construct()
{ {
$this->middleware('auth'); $this->middleware('auth');
} }
public function store(Request $request) public function store(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
'item' => 'required|integer', 'item' => 'required|integer',
]); ]);
$profile = Auth::user()->profile; $profile = Auth::user()->profile;
$status = Status::withCount('likes')->findOrFail($request->input('item')); $status = Status::withCount('likes')->findOrFail($request->input('item'));
$count = $status->likes_count; $count = $status->likes_count;
if($status->likes()->whereProfileId($profile->id)->count() !== 0) { if ($status->likes()->whereProfileId($profile->id)->count() !== 0) {
$like = Like::whereProfileId($profile->id)->whereStatusId($status->id)->firstOrFail(); $like = Like::whereProfileId($profile->id)->whereStatusId($status->id)->firstOrFail();
$like->forceDelete(); $like->forceDelete();
$count--; $count--;
} else { } else {
$like = new Like; $like = new Like();
$like->profile_id = $profile->id; $like->profile_id = $profile->id;
$like->status_id = $status->id; $like->status_id = $status->id;
$like->save(); $like->save();
$count++; $count++;
LikePipeline::dispatch($like); LikePipeline::dispatch($like);
} }
$likes = Like::whereProfileId($profile->id) $likes = Like::whereProfileId($profile->id)
->orderBy('id', 'desc') ->orderBy('id', 'desc')
->take(1000) ->take(1000)
->pluck('status_id'); ->pluck('status_id');
Cache::put('api:like-ids:user:'.$profile->id, $likes, 1440);
if($request->ajax()) { Cache::put('api:like-ids:user:'.$profile->id, $likes, 1440);
$response = ['code' => 200, 'msg' => 'Like saved', 'count' => $count];
} else {
$response = redirect($status->url());
}
return $response; if ($request->ajax()) {
$response = ['code' => 200, 'msg' => 'Like saved', 'count' => $count];
} else {
$response = redirect($status->url());
}
return $response;
} }
} }

View file

@ -2,8 +2,6 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request;
class MediaController extends Controller class MediaController extends Controller
{ {
// //

View file

@ -2,8 +2,6 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request;
class NotificationController extends Controller class NotificationController extends Controller
{ {
// //

View file

@ -2,133 +2,135 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Follower;
use App\Profile;
use App\Transformer\ActivityPub\ProfileTransformer;
use App\User;
use Auth;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Auth, Cache;
use App\{Follower, Profile, User};
use League\Fractal; use League\Fractal;
use App\Util\Lexer\Nickname;
use App\Util\Webfinger\Webfinger;
use App\Transformer\ActivityPub\{
ProfileOutbox,
ProfileTransformer
};
class ProfileController extends Controller class ProfileController extends Controller
{ {
public function show(Request $request, $username) public function show(Request $request, $username)
{ {
$user = Profile::whereUsername($username)->firstOrFail(); $user = Profile::whereUsername($username)->firstOrFail();
if($user->remote_url) { if ($user->remote_url) {
$settings = new \StdClass; $settings = new \StdClass();
$settings->crawlable = false; $settings->crawlable = false;
} else { } else {
$settings = User::whereUsername($username)->firstOrFail()->settings; $settings = User::whereUsername($username)->firstOrFail()->settings;
}
if($request->wantsJson() && config('pixelfed.activitypub_enabled')) {
return $this->showActivityPub($request, $user);
}
if($user->is_private == true) {
$can_access = $this->privateProfileCheck($user);
if($can_access !== true) {
abort(403);
} }
}
// TODO: refactor this mess if ($request->wantsJson() && config('pixelfed.activitypub_enabled')) {
$owner = Auth::check() && Auth::id() === $user->user_id; return $this->showActivityPub($request, $user);
$is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false; }
$is_admin = is_null($user->domain) ? $user->user->is_admin : false;
$timeline = $user->statuses() if ($user->is_private == true) {
$can_access = $this->privateProfileCheck($user);
if ($can_access !== true) {
abort(403);
}
}
// TODO: refactor this mess
$owner = Auth::check() && Auth::id() === $user->user_id;
$is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false;
$is_admin = is_null($user->domain) ? $user->user->is_admin : false;
$timeline = $user->statuses()
->whereHas('media') ->whereHas('media')
->whereNull('in_reply_to_id') ->whereNull('in_reply_to_id')
->whereNull('reblog_of_id') ->whereNull('reblog_of_id')
->orderBy('created_at','desc') ->orderBy('created_at', 'desc')
->withCount(['comments', 'likes']) ->withCount(['comments', 'likes'])
->simplePaginate(21); ->simplePaginate(21);
return view('profile.show', compact('user', 'settings', 'owner', 'is_following', 'is_admin', 'timeline')); return view('profile.show', compact('user', 'settings', 'owner', 'is_following', 'is_admin', 'timeline'));
} }
public function permalinkRedirect(Request $request, $username) public function permalinkRedirect(Request $request, $username)
{ {
$user = Profile::whereUsername($username)->firstOrFail(); $user = Profile::whereUsername($username)->firstOrFail();
$settings = User::whereUsername($username)->firstOrFail()->settings; $settings = User::whereUsername($username)->firstOrFail()->settings;
if($request->wantsJson() && config('pixelfed.activitypub_enabled')) { if ($request->wantsJson() && config('pixelfed.activitypub_enabled')) {
return $this->showActivityPub($request, $user); return $this->showActivityPub($request, $user);
} }
return redirect($user->url()); return redirect($user->url());
} }
protected function privateProfileCheck(Profile $profile) protected function privateProfileCheck(Profile $profile)
{ {
if(Auth::check() === false) { if (Auth::check() === false) {
return false; return false;
} }
$follower_ids = (array) $profile->followers()->pluck('followers.profile_id'); $follower_ids = (array) $profile->followers()->pluck('followers.profile_id');
$pid = Auth::user()->profile->id; $pid = Auth::user()->profile->id;
if(!in_array($pid, $follower_ids) && $pid !== $profile->id) { if (!in_array($pid, $follower_ids) && $pid !== $profile->id) {
return false; return false;
} }
return true; return true;
} }
public function showActivityPub(Request $request, $user) public function showActivityPub(Request $request, $user)
{ {
$fractal = new Fractal\Manager(); $fractal = new Fractal\Manager();
$resource = new Fractal\Resource\Item($user, new ProfileTransformer); $resource = new Fractal\Resource\Item($user, new ProfileTransformer());
$res = $fractal->createData($resource)->toArray(); $res = $fractal->createData($resource)->toArray();
return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
} }
public function showAtomFeed(Request $request, $user) public function showAtomFeed(Request $request, $user)
{ {
$profile = Profile::whereUsername($user)->firstOrFail(); $profile = Profile::whereUsername($user)->firstOrFail();
$items = $profile->statuses()->orderBy('created_at', 'desc')->take(10)->get(); $items = $profile->statuses()->orderBy('created_at', 'desc')->take(10)->get();
return response()->view('atom.user', compact('profile', 'items'))
return response()->view('atom.user', compact('profile', 'items'))
->header('Content-Type', 'application/atom+xml'); ->header('Content-Type', 'application/atom+xml');
} }
public function followers(Request $request, $username) public function followers(Request $request, $username)
{ {
$profile = Profile::whereUsername($username)->firstOrFail(); $profile = Profile::whereUsername($username)->firstOrFail();
// TODO: fix $profile/$user mismatch in profile & follower templates // TODO: fix $profile/$user mismatch in profile & follower templates
$user = $profile; $user = $profile;
$owner = Auth::check() && Auth::id() === $user->user_id; $owner = Auth::check() && Auth::id() === $user->user_id;
$is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false; $is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false;
$followers = $profile->followers()->orderBy('created_at','desc')->simplePaginate(12); $followers = $profile->followers()->orderBy('created_at', 'desc')->simplePaginate(12);
$is_admin = is_null($user->domain) ? $user->user->is_admin : false; $is_admin = is_null($user->domain) ? $user->user->is_admin : false;
return view('profile.followers', compact('user', 'profile', 'followers', 'owner', 'is_following', 'is_admin'));
return view('profile.followers', compact('user', 'profile', 'followers', 'owner', 'is_following', 'is_admin'));
} }
public function following(Request $request, $username) public function following(Request $request, $username)
{ {
$profile = Profile::whereUsername($username)->firstOrFail(); $profile = Profile::whereUsername($username)->firstOrFail();
// TODO: fix $profile/$user mismatch in profile & follower templates // TODO: fix $profile/$user mismatch in profile & follower templates
$user = $profile; $user = $profile;
$owner = Auth::check() && Auth::id() === $user->user_id; $owner = Auth::check() && Auth::id() === $user->user_id;
$is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false; $is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false;
$following = $profile->following()->orderBy('created_at','desc')->simplePaginate(12); $following = $profile->following()->orderBy('created_at', 'desc')->simplePaginate(12);
$is_admin = is_null($user->domain) ? $user->user->is_admin : false; $is_admin = is_null($user->domain) ? $user->user->is_admin : false;
return view('profile.following', compact('user', 'profile', 'following', 'owner', 'is_following', 'is_admin'));
return view('profile.following', compact('user', 'profile', 'following', 'owner', 'is_following', 'is_admin'));
} }
public function savedBookmarks(Request $request, $username) public function savedBookmarks(Request $request, $username)
{ {
if(Auth::check() === false || $username !== Auth::user()->username) { if (Auth::check() === false || $username !== Auth::user()->username) {
abort(403); abort(403);
} }
$user = Auth::user()->profile; $user = Auth::user()->profile;
$settings = User::whereUsername($username)->firstOrFail()->settings; $settings = User::whereUsername($username)->firstOrFail()->settings;
$owner = true; $owner = true;
$following = false; $following = false;
$timeline = $user->bookmarks()->withCount(['likes','comments'])->orderBy('created_at','desc')->simplePaginate(10); $timeline = $user->bookmarks()->withCount(['likes', 'comments'])->orderBy('created_at', 'desc')->simplePaginate(10);
$is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false; $is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false;
$is_admin = is_null($user->domain) ? $user->user->is_admin : false; $is_admin = is_null($user->domain) ? $user->user->is_admin : false;
return view('profile.show', compact('user', 'settings', 'owner', 'following', 'timeline', 'is_following', 'is_admin'));
return view('profile.show', compact('user', 'settings', 'owner', 'following', 'timeline', 'is_following', 'is_admin'));
} }
} }

View file

@ -2,9 +2,12 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Profile;
use App\Report;
use App\Status;
use App\User;
use Auth; use Auth;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\{Avatar, Profile, Report, Status, User};
class ReportController extends Controller class ReportController extends Controller
{ {
@ -12,95 +15,96 @@ class ReportController extends Controller
public function __construct() public function __construct()
{ {
$this->middleware('auth'); $this->middleware('auth');
} }
public function showForm(Request $request) public function showForm(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
'type' => 'required|alpha_dash', 'type' => 'required|alpha_dash',
'id' => 'required|integer|min:1' 'id' => 'required|integer|min:1',
]); ]);
return view('report.form');
return view('report.form');
} }
public function notInterestedForm(Request $request) public function notInterestedForm(Request $request)
{ {
return view('report.not-interested'); return view('report.not-interested');
} }
public function spamForm(Request $request) public function spamForm(Request $request)
{ {
return view('report.spam'); return view('report.spam');
} }
public function spamCommentForm(Request $request) public function spamCommentForm(Request $request)
{ {
return view('report.spam.comment'); return view('report.spam.comment');
} }
public function spamPostForm(Request $request) public function spamPostForm(Request $request)
{ {
return view('report.spam.post'); return view('report.spam.post');
} }
public function spamProfileForm(Request $request) public function spamProfileForm(Request $request)
{ {
return view('report.spam.profile'); return view('report.spam.profile');
} }
public function sensitiveCommentForm(Request $request) public function sensitiveCommentForm(Request $request)
{ {
return view('report.sensitive.comment'); return view('report.sensitive.comment');
} }
public function sensitivePostForm(Request $request) public function sensitivePostForm(Request $request)
{ {
return view('report.sensitive.post'); return view('report.sensitive.post');
} }
public function sensitiveProfileForm(Request $request) public function sensitiveProfileForm(Request $request)
{ {
return view('report.sensitive.profile'); return view('report.sensitive.profile');
} }
public function abusiveCommentForm(Request $request) public function abusiveCommentForm(Request $request)
{ {
return view('report.abusive.comment'); return view('report.abusive.comment');
} }
public function abusivePostForm(Request $request) public function abusivePostForm(Request $request)
{ {
return view('report.abusive.post'); return view('report.abusive.post');
} }
public function abusiveProfileForm(Request $request) public function abusiveProfileForm(Request $request)
{ {
return view('report.abusive.profile'); return view('report.abusive.profile');
} }
public function formStore(Request $request) public function formStore(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
'report' => 'required|alpha_dash', 'report' => 'required|alpha_dash',
'type' => 'required|alpha_dash', 'type' => 'required|alpha_dash',
'id' => 'required|integer|min:1', 'id' => 'required|integer|min:1',
'msg' => 'nullable|string|max:150' 'msg' => 'nullable|string|max:150',
]); ]);
$profile = Auth::user()->profile; $profile = Auth::user()->profile;
$reportType = $request->input('report'); $reportType = $request->input('report');
$object_id = $request->input('id'); $object_id = $request->input('id');
$object_type = $request->input('type'); $object_type = $request->input('type');
$msg = $request->input('msg'); $msg = $request->input('msg');
$object = null; $object = null;
$types = ['spam', 'sensitive', 'abusive']; $types = ['spam', 'sensitive', 'abusive'];
if(!in_array($reportType, $types)) { if (!in_array($reportType, $types)) {
return redirect('/timeline')->with('error', 'Invalid report type'); return redirect('/timeline')->with('error', 'Invalid report type');
} }
switch ($object_type) { switch ($object_type) {
case 'post': case 'post':
$object = Status::findOrFail($object_id); $object = Status::findOrFail($object_id);
$object_type = 'App\Status'; $object_type = 'App\Status';
@ -109,31 +113,30 @@ class ReportController extends Controller
->whereObjectType('App\Status') ->whereObjectType('App\Status')
->count(); ->count();
break; break;
default: default:
return redirect('/timeline')->with('error', 'Invalid report type'); return redirect('/timeline')->with('error', 'Invalid report type');
break; break;
} }
if($exists !== 0) { if ($exists !== 0) {
return redirect('/timeline')->with('error', 'You have already reported this!'); return redirect('/timeline')->with('error', 'You have already reported this!');
} }
if($object->profile_id == $profile->id) { if ($object->profile_id == $profile->id) {
return redirect('/timeline')->with('error', 'You cannot report your own content!'); return redirect('/timeline')->with('error', 'You cannot report your own content!');
} }
$report = new Report; $report = new Report();
$report->profile_id = $profile->id; $report->profile_id = $profile->id;
$report->user_id = Auth::id(); $report->user_id = Auth::id();
$report->object_id = $object->id; $report->object_id = $object->id;
$report->object_type = $object_type; $report->object_type = $object_type;
$report->reported_profile_id = $object->profile_id; $report->reported_profile_id = $object->profile_id;
$report->type = $request->input('report'); $report->type = $request->input('report');
$report->message = $request->input('msg'); $report->message = $request->input('msg');
$report->save(); $report->save();
return redirect('/timeline')->with('status', 'Report successfully sent!'); return redirect('/timeline')->with('status', 'Report successfully sent!');
} }
} }

View file

@ -2,7 +2,8 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\{Hashtag, Profile}; use App\Hashtag;
use App\Profile;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
@ -10,33 +11,34 @@ class SearchController extends Controller
{ {
public function searchAPI(Request $request, $tag) public function searchAPI(Request $request, $tag)
{ {
$res = Cache::remember('api:search:tag:' . $tag, 1440, function() use($tag) { $res = Cache::remember('api:search:tag:'.$tag, 1440, function () use ($tag) {
$res = Hashtag::where('slug', 'like', '%'.$tag.'%')->get(); $res = Hashtag::where('slug', 'like', '%'.$tag.'%')->get();
$tags = $res->map(function($item, $key) { $tags = $res->map(function ($item, $key) {
return [ return [
'count' => $item->posts()->count(), 'count' => $item->posts()->count(),
'url' => $item->url(), 'url' => $item->url(),
'type' => 'hashtag', 'type' => 'hashtag',
'value' => $item->name, 'value' => $item->name,
'tokens' => explode('-', $item->name), 'tokens' => explode('-', $item->name),
'name' => null 'name' => null,
]; ];
}); });
$res = Profile::where('username', 'like', '%'.$tag.'%')->get(); $res = Profile::where('username', 'like', '%'.$tag.'%')->get();
$profiles = $res->map(function($item, $key) { $profiles = $res->map(function ($item, $key) {
return [ return [
'count' => 0, 'count' => 0,
'url' => $item->url(), 'url' => $item->url(),
'type' => 'profile', 'type' => 'profile',
'value' => $item->username, 'value' => $item->username,
'tokens' => [$item->username], 'tokens' => [$item->username],
'name' => $item->name 'name' => $item->name,
]; ];
}); });
$tags = $tags->push($profiles[0]); $tags = $tags->push($profiles[0]);
return $tags;
});
return response()->json($res); return $tags;
});
return response()->json($res);
} }
} }

View file

@ -2,10 +2,15 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request; use App\AccountLog;
use App\{AccountLog, EmailVerification, Media, Profile, User}; use App\EmailVerification;
use Auth, DB; use App\Media;
use App\Profile;
use App\User;
use App\Util\Lexer\PrettyNumber; use App\Util\Lexer\PrettyNumber;
use Auth;
use DB;
use Illuminate\Http\Request;
class SettingsController extends Controller class SettingsController extends Controller
{ {
@ -16,229 +21,237 @@ class SettingsController extends Controller
public function home() public function home()
{ {
$id = Auth::user()->profile->id; $id = Auth::user()->profile->id;
$storage = []; $storage = [];
$used = Media::whereProfileId($id)->sum('size'); $used = Media::whereProfileId($id)->sum('size');
$storage['limit'] = config('pixelfed.max_account_size') * 1024; $storage['limit'] = config('pixelfed.max_account_size') * 1024;
$storage['used'] = $used; $storage['used'] = $used;
$storage['percentUsed'] = ceil($storage['used'] / $storage['limit'] * 100); $storage['percentUsed'] = ceil($storage['used'] / $storage['limit'] * 100);
$storage['limitPretty'] = PrettyNumber::size($storage['limit']); $storage['limitPretty'] = PrettyNumber::size($storage['limit']);
$storage['usedPretty'] = PrettyNumber::size($storage['used']); $storage['usedPretty'] = PrettyNumber::size($storage['used']);
return view('settings.home', compact('storage'));
return view('settings.home', compact('storage'));
} }
public function homeUpdate(Request $request) public function homeUpdate(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
'name' => 'required|string|max:' . config('pixelfed.max_name_length'), 'name' => 'required|string|max:'.config('pixelfed.max_name_length'),
'bio' => 'nullable|string|max:' . config('pixelfed.max_bio_length'), 'bio' => 'nullable|string|max:'.config('pixelfed.max_bio_length'),
'website' => 'nullable|url', 'website' => 'nullable|url',
'email' => 'nullable|email' 'email' => 'nullable|email',
]); ]);
$changes = false; $changes = false;
$name = $request->input('name'); $name = $request->input('name');
$bio = $request->input('bio'); $bio = $request->input('bio');
$website = $request->input('website'); $website = $request->input('website');
$email = $request->input('email'); $email = $request->input('email');
$user = Auth::user(); $user = Auth::user();
$profile = $user->profile; $profile = $user->profile;
$validate = config('pixelfed.enforce_email_verification'); $validate = config('pixelfed.enforce_email_verification');
if($user->email != $email) { if ($user->email != $email) {
$changes = true; $changes = true;
$user->email = $email; $user->email = $email;
if($validate) { if ($validate) {
$user->email_verified_at = null; $user->email_verified_at = null;
// Prevent old verifications from working // Prevent old verifications from working
EmailVerification::whereUserId($user->id)->delete(); EmailVerification::whereUserId($user->id)->delete();
} }
}
// Only allow email to be updated if not yet verified
if(!$validate || !$changes && $user->email_verified_at) {
if($profile->name != $name) {
$changes = true;
$user->name = $name;
$profile->name = $name;
} }
if(!$profile->website || $profile->website != $website) { // Only allow email to be updated if not yet verified
$changes = true; if (!$validate || !$changes && $user->email_verified_at) {
$profile->website = $website; if ($profile->name != $name) {
$changes = true;
$user->name = $name;
$profile->name = $name;
}
if (!$profile->website || $profile->website != $website) {
$changes = true;
$profile->website = $website;
}
if (!$profile->bio || !$profile->bio != $bio) {
$changes = true;
$profile->bio = $bio;
}
} }
if(!$profile->bio || !$profile->bio != $bio) { if ($changes === true) {
$changes = true; $user->save();
$profile->bio = $bio; $profile->save();
return redirect('/settings/home')->with('status', 'Profile successfully updated!');
} }
}
if($changes === true) { return redirect('/settings/home');
$user->save();
$profile->save();
return redirect('/settings/home')->with('status', 'Profile successfully updated!');
}
return redirect('/settings/home');
} }
public function password() public function password()
{ {
return view('settings.password'); return view('settings.password');
} }
public function passwordUpdate(Request $request) public function passwordUpdate(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
'current' => 'required|string', 'current' => 'required|string',
'password' => 'required|string', 'password' => 'required|string',
'password_confirmation' => 'required|string', 'password_confirmation' => 'required|string',
]); ]);
$current = $request->input('current'); $current = $request->input('current');
$new = $request->input('password'); $new = $request->input('password');
$confirm = $request->input('password_confirmation'); $confirm = $request->input('password_confirmation');
$user = Auth::user(); $user = Auth::user();
if(password_verify($current, $user->password) && $new === $confirm) { if (password_verify($current, $user->password) && $new === $confirm) {
$user->password = bcrypt($new); $user->password = bcrypt($new);
$user->save(); $user->save();
return redirect('/settings/home')->with('status', 'Password successfully updated!'); return redirect('/settings/home')->with('status', 'Password successfully updated!');
} }
return redirect('/settings/home')->with('error', 'There was an error with your request!');
return redirect('/settings/home')->with('error', 'There was an error with your request!');
} }
public function email() public function email()
{ {
return view('settings.email'); return view('settings.email');
} }
public function avatar() public function avatar()
{ {
return view('settings.avatar'); return view('settings.avatar');
} }
public function accessibility() public function accessibility()
{ {
$settings = Auth::user()->settings; $settings = Auth::user()->settings;
return view('settings.accessibility', compact('settings'));
return view('settings.accessibility', compact('settings'));
} }
public function accessibilityStore(Request $request) public function accessibilityStore(Request $request)
{ {
$settings = Auth::user()->settings; $settings = Auth::user()->settings;
$fields = [ $fields = [
'compose_media_descriptions', 'compose_media_descriptions',
'reduce_motion', 'reduce_motion',
'optimize_screen_reader', 'optimize_screen_reader',
'high_contrast_mode', 'high_contrast_mode',
'video_autoplay' 'video_autoplay',
]; ];
foreach($fields as $field) { foreach ($fields as $field) {
$form = $request->input($field); $form = $request->input($field);
if($form == 'on') { if ($form == 'on') {
$settings->{$field} = true; $settings->{$field} = true;
} else { } else {
$settings->{$field} = false; $settings->{$field} = false;
} }
$settings->save(); $settings->save();
} }
return redirect(route('settings.accessibility'))->with('status', 'Settings successfully updated!');
return redirect(route('settings.accessibility'))->with('status', 'Settings successfully updated!');
} }
public function notifications() public function notifications()
{ {
return view('settings.notifications'); return view('settings.notifications');
} }
public function privacy() public function privacy()
{ {
$settings = Auth::user()->settings; $settings = Auth::user()->settings;
$is_private = Auth::user()->profile->is_private; $is_private = Auth::user()->profile->is_private;
$settings['is_private'] = (bool) $is_private; $settings['is_private'] = (bool) $is_private;
return view('settings.privacy', compact('settings'));
return view('settings.privacy', compact('settings'));
} }
public function privacyStore(Request $request) public function privacyStore(Request $request)
{ {
$settings = Auth::user()->settings; $settings = Auth::user()->settings;
$profile = Auth::user()->profile; $profile = Auth::user()->profile;
$fields = [ $fields = [
'is_private', 'is_private',
'crawlable', 'crawlable',
'show_profile_follower_count', 'show_profile_follower_count',
'show_profile_following_count' 'show_profile_following_count',
]; ];
foreach($fields as $field) { foreach ($fields as $field) {
$form = $request->input($field); $form = $request->input($field);
if($field == 'is_private') { if ($field == 'is_private') {
if($form == 'on') { if ($form == 'on') {
$profile->{$field} = true; $profile->{$field} = true;
$settings->show_guests = false; $settings->show_guests = false;
$settings->show_discover = false; $settings->show_discover = false;
$profile->save(); $profile->save();
} else {
$profile->{$field} = false;
$profile->save();
}
} elseif ($field == 'crawlable') {
if ($form == 'on') {
$settings->{$field} = false;
} else {
$settings->{$field} = true;
}
} else { } else {
$profile->{$field} = false; if ($form == 'on') {
$profile->save(); $settings->{$field} = true;
} else {
$settings->{$field} = false;
}
} }
} elseif($field == 'crawlable') { $settings->save();
if($form == 'on') { }
$settings->{$field} = false;
} else { return redirect(route('settings.privacy'))->with('status', 'Settings successfully updated!');
$settings->{$field} = true;
}
} else {
if($form == 'on') {
$settings->{$field} = true;
} else {
$settings->{$field} = false;
}
}
$settings->save();
}
return redirect(route('settings.privacy'))->with('status', 'Settings successfully updated!');
} }
public function security() public function security()
{ {
$sessions = DB::table('sessions') $sessions = DB::table('sessions')
->whereUserId(Auth::id()) ->whereUserId(Auth::id())
->limit(20) ->limit(20)
->get(); ->get();
$activity = AccountLog::whereUserId(Auth::id()) $activity = AccountLog::whereUserId(Auth::id())
->orderBy('created_at','desc') ->orderBy('created_at', 'desc')
->limit(50) ->limit(50)
->get(); ->get();
return view('settings.security', compact('sessions', 'activity'));
return view('settings.security', compact('sessions', 'activity'));
} }
public function applications() public function applications()
{ {
return view('settings.applications'); return view('settings.applications');
} }
public function dataExport() public function dataExport()
{ {
return view('settings.dataexport'); return view('settings.dataexport');
} }
public function dataImport() public function dataImport()
{ {
return view('settings.import.home'); return view('settings.import.home');
} }
public function dataImportInstagram() public function dataImportInstagram()
{ {
return view('settings.import.instagram.home'); return view('settings.import.instagram.home');
} }
public function developers() public function developers()
{ {
return view('settings.developers'); return view('settings.developers');
} }
} }

View file

@ -2,20 +2,24 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App, Auth, Cache; use App;
use Illuminate\Http\Request; use App\Follower;
use App\{Follower, Profile, Status, User}; use App\Profile;
use App\Status;
use App\User;
use App\Util\Lexer\PrettyNumber; use App\Util\Lexer\PrettyNumber;
use Auth;
use Cache;
use Illuminate\Http\Request;
class SiteController extends Controller class SiteController extends Controller
{ {
public function home() public function home()
{ {
if(Auth::check()) { if (Auth::check()) {
return $this->homeTimeline(); return $this->homeTimeline();
} else { } else {
return $this->homeGuest(); return $this->homeGuest();
} }
} }
@ -26,40 +30,44 @@ class SiteController extends Controller
public function homeTimeline() public function homeTimeline()
{ {
// TODO: Use redis for timelines // TODO: Use redis for timelines
$following = Follower::whereProfileId(Auth::user()->profile->id)->pluck('following_id'); $following = Follower::whereProfileId(Auth::user()->profile->id)->pluck('following_id');
$following->push(Auth::user()->profile->id); $following->push(Auth::user()->profile->id);
$timeline = Status::whereIn('profile_id', $following) $timeline = Status::whereIn('profile_id', $following)
->whereHas('media') ->whereHas('media')
->orderBy('id','desc') ->orderBy('id', 'desc')
->withCount(['comments', 'likes', 'shares']) ->withCount(['comments', 'likes', 'shares'])
->simplePaginate(20); ->simplePaginate(20);
$type = 'personal'; $type = 'personal';
return view('timeline.template', compact('timeline', 'type'));
return view('timeline.template', compact('timeline', 'type'));
} }
public function changeLocale(Request $request, $locale) public function changeLocale(Request $request, $locale)
{ {
if(!App::isLocale($locale)) { if (!App::isLocale($locale)) {
return redirect()->back(); return redirect()->back();
} }
App::setLocale($locale); App::setLocale($locale);
return redirect()->back(); return redirect()->back();
} }
public function about() public function about()
{ {
$res = Cache::remember('site:page:about', 15, function() { $res = Cache::remember('site:page:about', 15, function () {
$statuses = Status::whereHas('media') $statuses = Status::whereHas('media')
->whereNull('in_reply_to_id') ->whereNull('in_reply_to_id')
->whereNull('reblog_of_id') ->whereNull('reblog_of_id')
->count(); ->count();
$statusCount = PrettyNumber::convert($statuses); $statusCount = PrettyNumber::convert($statuses);
$userCount = PrettyNumber::convert(User::count()); $userCount = PrettyNumber::convert(User::count());
$remoteCount = PrettyNumber::convert(Profile::whereNotNull('remote_url')->count()); $remoteCount = PrettyNumber::convert(Profile::whereNotNull('remote_url')->count());
$adminContact = User::whereIsAdmin(true)->first(); $adminContact = User::whereIsAdmin(true)->first();
return view('site.about')->with(compact('statusCount', 'userCount', 'remoteCount', 'adminContact'))->render();
return view('site.about')->with(compact('statusCount', 'userCount', 'remoteCount', 'adminContact'))->render();
}); });
return $res; return $res;
} }
} }

View file

@ -2,14 +2,18 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Auth, Cache;
use League\Fractal;
use Illuminate\Http\Request;
use Vinkla\Hashids\Facades\Hashids;
use App\{Media, Profile, Status, User};
use App\Jobs\ImageOptimizePipeline\ImageOptimize; use App\Jobs\ImageOptimizePipeline\ImageOptimize;
use App\Jobs\StatusPipeline\NewStatusPipeline;
use App\Jobs\StatusPipeline\StatusDelete;
use App\Media;
use App\Profile;
use App\Status;
use App\Transformer\ActivityPub\StatusTransformer; use App\Transformer\ActivityPub\StatusTransformer;
use App\Jobs\StatusPipeline\{NewStatusPipeline, StatusDelete}; use App\User;
use Auth;
use Cache;
use Illuminate\Http\Request;
use League\Fractal;
class StatusController extends Controller class StatusController extends Controller
{ {
@ -21,8 +25,8 @@ class StatusController extends Controller
->withCount(['likes', 'comments', 'media']) ->withCount(['likes', 'comments', 'media'])
->findOrFail($id); ->findOrFail($id);
if($request->wantsJson() && config('pixelfed.activitypub_enabled')) { if ($request->wantsJson() && config('pixelfed.activitypub_enabled')) {
return $this->showActivityPub($request, $status); return $this->showActivityPub($request, $status);
} }
$template = $this->detectTemplate($status); $template = $this->detectTemplate($status);
@ -34,25 +38,28 @@ class StatusController extends Controller
protected function detectTemplate($status) protected function detectTemplate($status)
{ {
$template = Cache::rememberForever('template:status:type:'.$status->id, function () use($status) { $template = Cache::rememberForever('template:status:type:'.$status->id, function () use ($status) {
$template = 'status.show.photo'; $template = 'status.show.photo';
if(!$status->media_path && $status->in_reply_to_id) { if (!$status->media_path && $status->in_reply_to_id) {
$template = 'status.reply'; $template = 'status.reply';
} }
if($status->media->count() > 1) { if ($status->media->count() > 1) {
$template = 'status.show.album'; $template = 'status.show.album';
} }
if($status->viewType() == 'video') { if ($status->viewType() == 'video') {
$template = 'status.show.video'; $template = 'status.show.video';
} }
return $template;
return $template;
}); });
return $template; return $template;
} }
public function compose() public function compose()
{ {
$this->authCheck(); $this->authCheck();
return view('status.compose'); return view('status.compose');
} }
@ -63,27 +70,27 @@ class StatusController extends Controller
$size = Media::whereUserId($user->id)->sum('size') / 1000; $size = Media::whereUserId($user->id)->sum('size') / 1000;
$limit = (int) config('pixelfed.max_account_size'); $limit = (int) config('pixelfed.max_account_size');
if($size >= $limit) { if ($size >= $limit) {
return redirect()->back()->with('error', 'You have exceeded your storage limit. Please click <a href="#">here</a> for more info.'); return redirect()->back()->with('error', 'You have exceeded your storage limit. Please click <a href="#">here</a> for more info.');
} }
$this->validate($request, [ $this->validate($request, [
'photo.*' => 'required|mimes:jpeg,png,gif|max:' . config('pixelfed.max_photo_size'), 'photo.*' => 'required|mimes:jpeg,png,gif|max:'.config('pixelfed.max_photo_size'),
'caption' => 'string|max:' . config('pixelfed.max_caption_length'), 'caption' => 'string|max:'.config('pixelfed.max_caption_length'),
'cw' => 'nullable|string', 'cw' => 'nullable|string',
'filter_class' => 'nullable|string', 'filter_class' => 'nullable|string',
'filter_name' => 'nullable|string', 'filter_name' => 'nullable|string',
]); ]);
if(count($request->file('photo')) > config('pixelfed.max_album_length')) { if (count($request->file('photo')) > config('pixelfed.max_album_length')) {
return redirect()->back()->with('error', 'Too many files, max limit per post: ' . config('pixelfed.max_album_length')); return redirect()->back()->with('error', 'Too many files, max limit per post: '.config('pixelfed.max_album_length'));
} }
$cw = $request->filled('cw') && $request->cw == 'on' ? true : false; $cw = $request->filled('cw') && $request->cw == 'on' ? true : false;
$monthHash = hash('sha1', date('Y') . date('m')); $monthHash = hash('sha1', date('Y').date('m'));
$userHash = hash('sha1', $user->id . (string) $user->created_at); $userHash = hash('sha1', $user->id.(string) $user->created_at);
$profile = $user->profile; $profile = $user->profile;
$status = new Status; $status = new Status();
$status->profile_id = $profile->id; $status->profile_id = $profile->id;
$status->caption = strip_tags($request->caption); $status->caption = strip_tags($request->caption);
$status->is_nsfw = $cw; $status->is_nsfw = $cw;
@ -93,47 +100,47 @@ class StatusController extends Controller
$photos = $request->file('photo'); $photos = $request->file('photo');
$order = 1; $order = 1;
foreach ($photos as $k => $v) { foreach ($photos as $k => $v) {
$storagePath = "public/m/{$monthHash}/{$userHash}"; $storagePath = "public/m/{$monthHash}/{$userHash}";
$path = $v->store($storagePath); $path = $v->store($storagePath);
$hash = \hash_file('sha256', $v); $hash = \hash_file('sha256', $v);
$media = new Media; $media = new Media();
$media->status_id = $status->id; $media->status_id = $status->id;
$media->profile_id = $profile->id; $media->profile_id = $profile->id;
$media->user_id = $user->id; $media->user_id = $user->id;
$media->media_path = $path; $media->media_path = $path;
$media->original_sha256 = $hash; $media->original_sha256 = $hash;
$media->size = $v->getClientSize(); $media->size = $v->getClientSize();
$media->mime = $v->getClientMimeType(); $media->mime = $v->getClientMimeType();
$media->filter_class = $request->input('filter_class'); $media->filter_class = $request->input('filter_class');
$media->filter_name = $request->input('filter_name'); $media->filter_name = $request->input('filter_name');
$media->order = $order; $media->order = $order;
$media->save(); $media->save();
ImageOptimize::dispatch($media); ImageOptimize::dispatch($media);
$order++; $order++;
} }
NewStatusPipeline::dispatch($status); NewStatusPipeline::dispatch($status);
// TODO: Send to subscribers // TODO: Send to subscribers
return redirect($status->url()); return redirect($status->url());
} }
public function delete(Request $request) public function delete(Request $request)
{ {
if(!Auth::check()) { if (!Auth::check()) {
abort(403); abort(403);
} }
$this->validate($request, [ $this->validate($request, [
'type' => 'required|string', 'type' => 'required|string',
'item' => 'required|integer|min:1' 'item' => 'required|integer|min:1',
]); ]);
$status = Status::findOrFail($request->input('item')); $status = Status::findOrFail($request->input('item'));
if($status->profile_id === Auth::user()->profile->id || Auth::user()->is_admin == true) { if ($status->profile_id === Auth::user()->profile->id || Auth::user()->is_admin == true) {
StatusDelete::dispatch($status); StatusDelete::dispatch($status);
} }
return redirect(Auth::user()->url()); return redirect(Auth::user()->url());
@ -153,26 +160,26 @@ class StatusController extends Controller
$exists = Status::whereProfileId(Auth::user()->profile->id) $exists = Status::whereProfileId(Auth::user()->profile->id)
->whereReblogOfId($status->id) ->whereReblogOfId($status->id)
->count(); ->count();
if($exists !== 0) { if ($exists !== 0) {
$shares = Status::whereProfileId(Auth::user()->profile->id) $shares = Status::whereProfileId(Auth::user()->profile->id)
->whereReblogOfId($status->id) ->whereReblogOfId($status->id)
->get(); ->get();
foreach($shares as $share) { foreach ($shares as $share) {
$share->delete(); $share->delete();
$count--; $count--;
} }
} else { } else {
$share = new Status; $share = new Status();
$share->profile_id = $profile->id; $share->profile_id = $profile->id;
$share->reblog_of_id = $status->id; $share->reblog_of_id = $status->id;
$share->save(); $share->save();
$count++; $count++;
} }
if($request->ajax()) { if ($request->ajax()) {
$response = ['code' => 200, 'msg' => 'Share saved', 'count' => $count]; $response = ['code' => 200, 'msg' => 'Share saved', 'count' => $count];
} else { } else {
$response = redirect($status->url()); $response = redirect($status->url());
} }
return $response; return $response;
@ -180,10 +187,11 @@ class StatusController extends Controller
public function showActivityPub(Request $request, $status) public function showActivityPub(Request $request, $status)
{ {
$fractal = new Fractal\Manager(); $fractal = new Fractal\Manager();
$resource = new Fractal\Resource\Item($status, new StatusTransformer); $resource = new Fractal\Resource\Item($status, new StatusTransformer());
$res = $fractal->createData($resource)->toArray(); $res = $fractal->createData($resource)->toArray();
return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
} }
public function edit(Request $request, $username, $id) public function edit(Request $request, $username, $id)
@ -193,10 +201,10 @@ class StatusController extends Controller
$status = Status::whereProfileId($user->id) $status = Status::whereProfileId($user->id)
->with(['media']) ->with(['media'])
->findOrFail($id); ->findOrFail($id);
return view('status.edit', compact('user', 'status')); return view('status.edit', compact('user', 'status'));
} }
public function editStore(Request $request, $username, $id) public function editStore(Request $request, $username, $id)
{ {
$this->authCheck(); $this->authCheck();
@ -206,9 +214,9 @@ class StatusController extends Controller
->findOrFail($id); ->findOrFail($id);
$this->validate($request, [ $this->validate($request, [
'id' => 'required|integer|min:1', 'id' => 'required|integer|min:1',
'caption' => 'nullable', 'caption' => 'nullable',
'filter' => 'nullable|alpha_dash|max:30' 'filter' => 'nullable|alpha_dash|max:30',
]); ]);
$id = $request->input('id'); $id = $request->input('id');
@ -221,27 +229,27 @@ class StatusController extends Controller
$changed = false; $changed = false;
if($media->caption != $caption) { if ($media->caption != $caption) {
$media->caption = $caption; $media->caption = $caption;
$changed = true; $changed = true;
} }
if($media->filter_class != $filter) { if ($media->filter_class != $filter) {
$media->filter_class = $filter; $media->filter_class = $filter;
$changed = true; $changed = true;
} }
if($changed === true) { if ($changed === true) {
$media->save(); $media->save();
} }
return response()->json([], 200); return response()->json([], 200);
} }
protected function authCheck() protected function authCheck()
{ {
if(Auth::check() == false) if (Auth::check() == false) {
{ abort(403);
abort(403);
} }
} }
} }

View file

@ -2,10 +2,6 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Profile;
class StoryController extends Controller class StoryController extends Controller
{ {
}
}

View file

@ -2,57 +2,61 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request; use App\Follower;
use App\Profile;
use App\Status;
use App\User;
use App\UserFilter;
use Auth; use Auth;
use App\{Follower, Profile, Status, User, UserFilter};
class TimelineController extends Controller class TimelineController extends Controller
{ {
public function __construct() public function __construct()
{ {
$this->middleware('auth'); $this->middleware('auth');
} }
public function personal() public function personal()
{ {
$pid = Auth::user()->profile->id; $pid = Auth::user()->profile->id;
// TODO: Use redis for timelines // TODO: Use redis for timelines
$following = Follower::whereProfileId($pid)->pluck('following_id'); $following = Follower::whereProfileId($pid)->pluck('following_id');
$following->push($pid); $following->push($pid);
$filtered = UserFilter::whereUserId($pid) $filtered = UserFilter::whereUserId($pid)
->whereFilterableType('App\Profile') ->whereFilterableType('App\Profile')
->whereIn('filter_type', ['mute', 'block']) ->whereIn('filter_type', ['mute', 'block'])
->pluck('filterable_id'); ->pluck('filterable_id');
$timeline = Status::whereIn('profile_id', $following) $timeline = Status::whereIn('profile_id', $following)
->whereNotIn('profile_id', $filtered) ->whereNotIn('profile_id', $filtered)
->orderBy('id','desc') ->orderBy('id', 'desc')
->withCount(['comments', 'likes']) ->withCount(['comments', 'likes'])
->simplePaginate(20); ->simplePaginate(20);
$type = 'personal'; $type = 'personal';
return view('timeline.template', compact('timeline', 'type'));
return view('timeline.template', compact('timeline', 'type'));
} }
public function local() public function local()
{ {
// TODO: Use redis for timelines // TODO: Use redis for timelines
// $timeline = Timeline::build()->local(); // $timeline = Timeline::build()->local();
$pid = Auth::user()->profile->id; $pid = Auth::user()->profile->id;
$filtered = UserFilter::whereUserId($pid) $filtered = UserFilter::whereUserId($pid)
->whereFilterableType('App\Profile') ->whereFilterableType('App\Profile')
->whereIn('filter_type', ['mute', 'block']) ->whereIn('filter_type', ['mute', 'block'])
->pluck('filterable_id'); ->pluck('filterable_id');
$private = Profile::whereIsPrivate(true)->pluck('id'); $private = Profile::whereIsPrivate(true)->pluck('id');
$filtered = $filtered->merge($private); $filtered = $filtered->merge($private);
$timeline = Status::whereHas('media') $timeline = Status::whereHas('media')
->whereNotIn('profile_id', $filtered) ->whereNotIn('profile_id', $filtered)
->whereNull('in_reply_to_id') ->whereNull('in_reply_to_id')
->whereNull('reblog_of_id') ->whereNull('reblog_of_id')
->withCount(['comments', 'likes']) ->withCount(['comments', 'likes'])
->orderBy('id','desc') ->orderBy('id', 'desc')
->simplePaginate(20); ->simplePaginate(20);
$type = 'local'; $type = 'local';
return view('timeline.template', compact('timeline', 'type'));
}
return view('timeline.template', compact('timeline', 'type'));
}
} }

View file

@ -51,15 +51,15 @@ class Kernel extends HttpKernel
* @var array * @var array
*/ */
protected $routeMiddleware = [ protected $routeMiddleware = [
'admin' => \App\Http\Middleware\Admin::class, 'admin' => \App\Http\Middleware\Admin::class,
'auth' => \Illuminate\Auth\Middleware\Authenticate::class, 'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'validemail' => \App\Http\Middleware\EmailVerificationCheck::class, 'validemail' => \App\Http\Middleware\EmailVerificationCheck::class,
]; ];
} }

View file

@ -2,20 +2,22 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use Auth, Closure; use Auth;
use Closure;
class Admin class Admin
{ {
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Closure $next * @param \Closure $next
*
* @return mixed * @return mixed
*/ */
public function handle($request, Closure $next) public function handle($request, Closure $next)
{ {
if(Auth::check() == false || Auth::user()->is_admin == false) { if (Auth::check() == false || Auth::user()->is_admin == false) {
return redirect(config('app.url')); return redirect(config('app.url'));
} }

View file

@ -2,26 +2,28 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use Auth, Closure; use Closure;
class EmailVerificationCheck class EmailVerificationCheck
{ {
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Closure $next * @param \Closure $next
*
* @return mixed * @return mixed
*/ */
public function handle($request, Closure $next) public function handle($request, Closure $next)
{ {
if($request->user() && if ($request->user() &&
config('pixelfed.enforce_email_verification') && config('pixelfed.enforce_email_verification') &&
is_null($request->user()->email_verified_at) && is_null($request->user()->email_verified_at) &&
!$request->is('i/verify-email', 'log*', 'i/confirm-email/*', 'settings/home') !$request->is('i/verify-email', 'log*', 'i/confirm-email/*', 'settings/home')
) { ) {
return redirect('/i/verify-email'); return redirect('/i/verify-email');
} }
return $next($request); return $next($request);
} }
} }

View file

@ -10,9 +10,10 @@ class RedirectIfAuthenticated
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Closure $next * @param \Closure $next
* @param string|null $guard * @param string|null $guard
*
* @return mixed * @return mixed
*/ */
public function handle($request, Closure $next, $guard = null) public function handle($request, Closure $next, $guard = null)

View file

@ -2,8 +2,8 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Fideloper\Proxy\TrustProxies as Middleware; use Fideloper\Proxy\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware class TrustProxies extends Middleware
{ {

View file

@ -2,14 +2,15 @@
namespace App\Jobs\AvatarPipeline; namespace App\Jobs\AvatarPipeline;
use \Carbon\Carbon; use App\Avatar;
use Image as Intervention; use App\Profile;
use App\{Avatar, Profile}; use Carbon\Carbon;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Image as Intervention;
class AvatarOptimize implements ShouldQueue class AvatarOptimize implements ShouldQueue
{ {
@ -54,16 +55,15 @@ class AvatarOptimize implements ShouldQueue
$avatar->save(); $avatar->save();
$this->deleteOldAvatar($avatar->media_path, $this->current); $this->deleteOldAvatar($avatar->media_path, $this->current);
} catch (Exception $e) { } catch (Exception $e) {
} }
} }
protected function deleteOldAvatar($new, $current) protected function deleteOldAvatar($new, $current)
{ {
if(storage_path('app/' . $new) == $current) { if (storage_path('app/'.$new) == $current) {
return; return;
} }
if(is_file($current)) { if (is_file($current)) {
@unlink($current); @unlink($current);
} }
} }

View file

@ -2,23 +2,24 @@
namespace App\Jobs\AvatarPipeline; namespace App\Jobs\AvatarPipeline;
use App\{Avatar, Profile}; use App\Avatar;
use Illuminate\Bus\Queueable; use App\Profile;
use Illuminate\Queue\SerializesModels; use App\Util\Identicon\Preprocessor\HashPreprocessor;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Str;
use Bitverse\Identicon\Identicon;
use Bitverse\Identicon\Color\Color; use Bitverse\Identicon\Color\Color;
use Bitverse\Identicon\Generator\RingsGenerator; use Bitverse\Identicon\Generator\RingsGenerator;
use App\Util\Identicon\Preprocessor\HashPreprocessor; use Bitverse\Identicon\Identicon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class CreateAvatar implements ShouldQueue class CreateAvatar implements ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $profile; protected $profile;
/** /**
* Create a new job instance. * Create a new job instance.
* *
@ -44,66 +45,63 @@ class CreateAvatar implements ShouldQueue
$identicon = new Identicon(new HashPreprocessor('sha256'), $generator); $identicon = new Identicon(new HashPreprocessor('sha256'), $generator);
$hash = $username . str_random(12); $hash = $username.str_random(12);
$icon = $identicon->getIcon($hash); $icon = $identicon->getIcon($hash);
try { try {
$baseDir = storage_path('app/public/avatars');
$baseDir = storage_path('app/public/avatars'); if (!is_dir($baseDir)) {
if(!is_dir($baseDir)) { mkdir($baseDir);
mkdir($baseDir); }
}
$prefix = $profile->id; $prefix = $profile->id;
$padded = str_pad($prefix, 12, 0, STR_PAD_LEFT); $padded = str_pad($prefix, 12, 0, STR_PAD_LEFT);
$parts = str_split($padded, 3); $parts = str_split($padded, 3);
foreach($parts as $k => $part) { foreach ($parts as $k => $part) {
if($k == 0) { if ($k == 0) {
$prefix = storage_path('app/public/avatars/'.$parts[0]); $prefix = storage_path('app/public/avatars/'.$parts[0]);
if(!is_dir($prefix)) { if (!is_dir($prefix)) {
mkdir($prefix); mkdir($prefix);
}
}
if ($k == 1) {
$prefix = storage_path('app/public/avatars/'.$parts[0].'/'.$parts[1]);
if (!is_dir($prefix)) {
mkdir($prefix);
}
}
if ($k == 2) {
$prefix = storage_path('app/public/avatars/'.$parts[0].'/'.$parts[1].'/'.$parts[2]);
if (!is_dir($prefix)) {
mkdir($prefix);
}
}
if ($k == 3) {
$avatarpath = 'public/avatars/'.$parts[0].'/'.$parts[1].'/'.$parts[2].'/'.$parts[3];
$prefix = storage_path('app/'.$avatarpath);
if (!is_dir($prefix)) {
mkdir($prefix);
}
} }
} }
if($k == 1) { $dir = storage_path('app/'.$avatarpath);
$prefix = storage_path('app/public/avatars/'.$parts[0].'/'.$parts[1]); //$dir = storage_path('app/public/avatars/'.$prefix);
if(!is_dir($prefix)) { if (!is_dir($dir)) {
mkdir($prefix); mkdir($dir);
}
} }
if($k == 2) { //$path = 'public/avatars/' . $prefix . '/avatar.svg';
$prefix = storage_path('app/public/avatars/'.$parts[0].'/'.$parts[1].'/'.$parts[2]); $path = $avatarpath.'/avatar.svg';
if(!is_dir($prefix)) { $basePath = storage_path('app/'.$path);
mkdir($prefix); file_put_contents($basePath, $icon);
}
}
if($k == 3) {
$avatarpath = 'public/avatars/'.$parts[0].'/'.$parts[1].'/'.$parts[2].'/'.$parts[3];
$prefix = storage_path('app/'.$avatarpath);
if(!is_dir($prefix)) {
mkdir($prefix);
}
}
}
$dir = storage_path('app/'.$avatarpath);
//$dir = storage_path('app/public/avatars/'.$prefix);
if(!is_dir($dir)) {
mkdir($dir);
}
//$path = 'public/avatars/' . $prefix . '/avatar.svg';
$path = $avatarpath . '/avatar.svg';
$basePath = storage_path('app/' . $path);
file_put_contents($basePath, $icon);
} catch (Exception $e) { } catch (Exception $e) {
} }
$avatar = new Avatar; $avatar = new Avatar();
$avatar->profile_id = $profile->id; $avatar->profile_id = $profile->id;
$avatar->media_path = $path; $avatar->media_path = $path;
$avatar->thumb_path = $path; $avatar->thumb_path = $path;
$avatar->change_count = 0; $avatar->change_count = 0;
$avatar->last_processed_at = \Carbon\Carbon::now(); $avatar->last_processed_at = \Carbon\Carbon::now();
$avatar->save(); $avatar->save();
} }
} }

View file

@ -2,14 +2,16 @@
namespace App\Jobs\CommentPipeline; namespace App\Jobs\CommentPipeline;
use Cache, Log, Redis; use App\Notification;
use App\{Like, Notification, Status}; use App\Status;
use App\Util\Lexer\Hashtag as HashtagLexer; use Cache;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Log;
use Redis;
class CommentPipeline implements ShouldQueue class CommentPipeline implements ShouldQueue
{ {
@ -42,13 +44,12 @@ class CommentPipeline implements ShouldQueue
$target = $status->profile; $target = $status->profile;
$actor = $comment->profile; $actor = $comment->profile;
if($actor->id === $target->id) { if ($actor->id === $target->id) {
return true; return true;
} }
try { try {
$notification = new Notification();
$notification = new Notification;
$notification->profile_id = $target->id; $notification->profile_id = $target->id;
$notification->actor_id = $actor->id; $notification->actor_id = $actor->id;
$notification->action = 'comment'; $notification->action = 'comment';
@ -58,16 +59,14 @@ class CommentPipeline implements ShouldQueue
$notification->item_type = "App\Status"; $notification->item_type = "App\Status";
$notification->save(); $notification->save();
Cache::forever('notification.' . $notification->id, $notification); Cache::forever('notification.'.$notification->id, $notification);
$redis = Redis::connection(); $redis = Redis::connection();
$nkey = config('cache.prefix').':user.' . $target->id . '.notifications'; $nkey = config('cache.prefix').':user.'.$target->id.'.notifications';
$redis->lpush($nkey, $notification->id); $redis->lpush($nkey, $notification->id);
} catch (Exception $e) { } catch (Exception $e) {
Log::error($e); Log::error($e);
} }
} }
} }

View file

@ -2,14 +2,15 @@
namespace App\Jobs\FollowPipeline; namespace App\Jobs\FollowPipeline;
use Cache, Log, Redis; use App\Notification;
use App\{Like, Notification}; use Cache;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use App\Jobs\FollowPipeline\FollowDiscover; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Log;
use Redis;
class FollowPipeline implements ShouldQueue class FollowPipeline implements ShouldQueue
{ {
@ -39,8 +40,7 @@ class FollowPipeline implements ShouldQueue
$target = $follower->target; $target = $follower->target;
try { try {
$notification = new Notification();
$notification = new Notification;
$notification->profile_id = $target->id; $notification->profile_id = $target->id;
$notification->actor_id = $actor->id; $notification->actor_id = $actor->id;
$notification->action = 'follow'; $notification->action = 'follow';
@ -50,13 +50,12 @@ class FollowPipeline implements ShouldQueue
$notification->item_type = "App\Profile"; $notification->item_type = "App\Profile";
$notification->save(); $notification->save();
Cache::forever('notification.' . $notification->id, $notification); Cache::forever('notification.'.$notification->id, $notification);
$redis = Redis::connection(); $redis = Redis::connection();
$nkey = config('cache.prefix').':user.' . $target->id . '.notifications'; $nkey = config('cache.prefix').':user.'.$target->id.'.notifications';
$redis->lpush($nkey, $notification->id); $redis->lpush($nkey, $notification->id);
} catch (Exception $e) { } catch (Exception $e) {
Log::error($e); Log::error($e);
} }

View file

@ -2,19 +2,17 @@
namespace App\Jobs\ImageOptimizePipeline; namespace App\Jobs\ImageOptimizePipeline;
use Carbon\Carbon; use App\Media;
use ImageOptimizer;
use App\{Media, Status};
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ImageOptimize implements ShouldQueue class ImageOptimize implements ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $media; protected $media;
/** /**
@ -35,8 +33,8 @@ class ImageOptimize implements ShouldQueue
public function handle() public function handle()
{ {
$media = $this->media; $media = $this->media;
$path = storage_path('app/'. $media->media_path); $path = storage_path('app/'.$media->media_path);
if(!is_file($path)) { if (!is_file($path)) {
return; return;
} }

View file

@ -2,15 +2,13 @@
namespace App\Jobs\ImageOptimizePipeline; namespace App\Jobs\ImageOptimizePipeline;
use Carbon\Carbon; use App\Media;
use ImageOptimizer;
use App\{Media, Status};
use App\Util\Media\Image; use App\Util\Media\Image;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ImageResize implements ShouldQueue class ImageResize implements ShouldQueue
{ {
@ -36,16 +34,15 @@ class ImageResize implements ShouldQueue
public function handle() public function handle()
{ {
$media = $this->media; $media = $this->media;
$path = storage_path('app/'. $media->media_path); $path = storage_path('app/'.$media->media_path);
if(!is_file($path)) { if (!is_file($path)) {
return; return;
} }
try { try {
$img = new Image; $img = new Image();
$img->resizeImage($media); $img->resizeImage($media);
} catch (Exception $e) { } catch (Exception $e) {
} }
ImageThumbnail::dispatch($media); ImageThumbnail::dispatch($media);

View file

@ -2,14 +2,14 @@
namespace App\Jobs\ImageOptimizePipeline; namespace App\Jobs\ImageOptimizePipeline;
use Carbon\Carbon; use App\Media;
use App\{Media, Status};
use App\Util\Media\Image; use App\Util\Media\Image;
use Carbon\Carbon;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ImageThumbnail implements ShouldQueue class ImageThumbnail implements ShouldQueue
{ {
@ -35,16 +35,15 @@ class ImageThumbnail implements ShouldQueue
public function handle() public function handle()
{ {
$media = $this->media; $media = $this->media;
$path = storage_path('app/'. $media->media_path); $path = storage_path('app/'.$media->media_path);
if(!is_file($path)) { if (!is_file($path)) {
return; return;
} }
try { try {
$img = new Image; $img = new Image();
$img->resizeThumbnail($media); $img->resizeThumbnail($media);
} catch (Exception $e) { } catch (Exception $e) {
} }
$media->processed_at = Carbon::now(); $media->processed_at = Carbon::now();

View file

@ -2,13 +2,13 @@
namespace App\Jobs\ImageOptimizePipeline; namespace App\Jobs\ImageOptimizePipeline;
use ImageOptimizer; use App\Media;
use App\{Media, Status};
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use ImageOptimizer;
class ImageUpdate implements ShouldQueue class ImageUpdate implements ShouldQueue
{ {
@ -19,7 +19,7 @@ class ImageUpdate implements ShouldQueue
protected $protectedMimes = [ protected $protectedMimes = [
'image/gif', 'image/gif',
'image/bmp', 'image/bmp',
'video/mp4' 'video/mp4',
]; ];
/** /**
@ -40,18 +40,18 @@ class ImageUpdate implements ShouldQueue
public function handle() public function handle()
{ {
$media = $this->media; $media = $this->media;
$path = storage_path('app/'. $media->media_path); $path = storage_path('app/'.$media->media_path);
$thumb = storage_path('app/'. $media->thumbnail_path); $thumb = storage_path('app/'.$media->thumbnail_path);
try { try {
if(!in_array($media->mime, $this->protectedMimes)) if (!in_array($media->mime, $this->protectedMimes)) {
{
ImageOptimizer::optimize($thumb); ImageOptimizer::optimize($thumb);
ImageOptimizer::optimize($path); ImageOptimizer::optimize($path);
} }
} catch (Exception $e) { } catch (Exception $e) {
return; return;
} }
if(!is_file($path) || !is_file($thumb)) { if (!is_file($path) || !is_file($thumb)) {
return; return;
} }
$photo_size = filesize($path); $photo_size = filesize($path);

View file

@ -5,10 +5,10 @@ namespace App\Jobs\InboxPipeline;
use App\Profile; use App\Profile;
use App\Util\ActivityPub\Inbox; use App\Util\ActivityPub\Inbox;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class InboxWorker implements ShouldQueue class InboxWorker implements ShouldQueue
{ {
@ -39,5 +39,4 @@ class InboxWorker implements ShouldQueue
{ {
(new Inbox($this->request, $this->profile, $this->payload))->handle(); (new Inbox($this->request, $this->profile, $this->payload))->handle();
} }
} }

View file

@ -2,13 +2,12 @@
namespace App\Jobs\InboxPipeline; namespace App\Jobs\InboxPipeline;
use App\Profile;
use App\Util\ActivityPub\Inbox; use App\Util\ActivityPub\Inbox;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SharedInboxWorker implements ShouldQueue class SharedInboxWorker implements ShouldQueue
{ {

View file

@ -2,19 +2,23 @@
namespace App\Jobs\LikePipeline; namespace App\Jobs\LikePipeline;
use Cache, Log, Redis; use App\Like;
use App\{Like, Notification}; use App\Notification;
use Cache;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Log;
use Redis;
class LikePipeline implements ShouldQueue class LikePipeline implements ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $like; protected $like;
/** /**
* Create a new job instance. * Create a new job instance.
* *
@ -37,7 +41,7 @@ class LikePipeline implements ShouldQueue
$status = $this->like->status; $status = $this->like->status;
$actor = $this->like->actor; $actor = $this->like->actor;
if($status->url !== null) { if ($status->url !== null) {
// Ignore notifications to remote statuses // Ignore notifications to remote statuses
return; return;
} }
@ -49,13 +53,12 @@ class LikePipeline implements ShouldQueue
->whereItemType('App\Status') ->whereItemType('App\Status')
->count(); ->count();
if($actor->id === $status->profile_id || $exists !== 0) { if ($actor->id === $status->profile_id || $exists !== 0) {
return true; return true;
} }
try { try {
$notification = new Notification();
$notification = new Notification;
$notification->profile_id = $status->profile_id; $notification->profile_id = $status->profile_id;
$notification->actor_id = $actor->id; $notification->actor_id = $actor->id;
$notification->action = 'like'; $notification->action = 'like';
@ -65,12 +68,11 @@ class LikePipeline implements ShouldQueue
$notification->item_type = "App\Status"; $notification->item_type = "App\Status";
$notification->save(); $notification->save();
Cache::forever('notification.' . $notification->id, $notification); Cache::forever('notification.'.$notification->id, $notification);
$redis = Redis::connection();
$key = config('cache.prefix').':user.' . $status->profile_id . '.notifications';
$redis->lpush($key, $notification->id);
$redis = Redis::connection();
$key = config('cache.prefix').':user.'.$status->profile_id.'.notifications';
$redis->lpush($key, $notification->id);
} catch (Exception $e) { } catch (Exception $e) {
Log::error($e); Log::error($e);
} }

View file

@ -2,13 +2,14 @@
namespace App\Jobs\MentionPipeline; namespace App\Jobs\MentionPipeline;
use Cache, Log, Redis; use App\Mention;
use App\{Mention, Notification, Profile, Status}; use App\Notification;
use App\Status;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class MentionPipeline implements ShouldQueue class MentionPipeline implements ShouldQueue
{ {
@ -35,7 +36,6 @@ class MentionPipeline implements ShouldQueue
*/ */
public function handle() public function handle()
{ {
$status = $this->status; $status = $this->status;
$mention = $this->mention; $mention = $this->mention;
$actor = $this->status->profile; $actor = $this->status->profile;
@ -48,13 +48,12 @@ class MentionPipeline implements ShouldQueue
->whereItemType('App\Status') ->whereItemType('App\Status')
->count(); ->count();
if($actor->id === $target || $exists !== 0) { if ($actor->id === $target || $exists !== 0) {
return true; return true;
} }
try { try {
$notification = new Notification();
$notification = new Notification;
$notification->profile_id = $target; $notification->profile_id = $target;
$notification->actor_id = $actor->id; $notification->actor_id = $actor->id;
$notification->action = 'mention'; $notification->action = 'mention';
@ -63,10 +62,7 @@ class MentionPipeline implements ShouldQueue
$notification->item_id = $status->id; $notification->item_id = $status->id;
$notification->item_type = "App\Status"; $notification->item_type = "App\Status";
$notification->save(); $notification->save();
} catch (Exception $e) { } catch (Exception $e) {
} }
} }
} }

View file

@ -2,18 +2,20 @@
namespace App\Jobs\RemoteFollowPipeline; namespace App\Jobs\RemoteFollowPipeline;
use Zttp\Zttp; use App\Jobs\ImageOptimizePipeline\ImageThumbnail;
use Log, Storage; use App\Jobs\StatusPipeline\NewStatusPipeline;
use App\Media;
use App\Status;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Http\File;
use App\{Media, Profile, Status};
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use App\Jobs\StatusPipeline\NewStatusPipeline; use Illuminate\Http\File;
use App\Jobs\ImageOptimizePipeline\ImageThumbnail; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Log;
use Storage;
use Zttp\Zttp;
class RemoteFollowImportRecent implements ShouldQueue class RemoteFollowImportRecent implements ShouldQueue
{ {
@ -42,7 +44,7 @@ class RemoteFollowImportRecent implements ShouldQueue
'image/jpg', 'image/jpg',
'image/jpeg', 'image/jpeg',
'image/png', 'image/png',
'image/gif' 'image/gif',
]; ];
} }
@ -62,7 +64,7 @@ class RemoteFollowImportRecent implements ShouldQueue
$url = ($url == false) ? $this->actor['outbox'] : $url; $url = ($url == false) ? $this->actor['outbox'] : $url;
$response = Zttp::withHeaders([ $response = Zttp::withHeaders([
'User-Agent' => 'PixelFedBot v0.1 - https://pixelfed.org' 'User-Agent' => 'PixelFedBot v0.1 - https://pixelfed.org',
])->get($url); ])->get($url);
$this->outbox = $response->json(); $this->outbox = $response->json();
@ -73,13 +75,14 @@ class RemoteFollowImportRecent implements ShouldQueue
{ {
$types = ['OrderedCollection', 'OrderedCollectionPage']; $types = ['OrderedCollection', 'OrderedCollectionPage'];
if(isset($outbox['totalItems']) && $outbox['totalItems'] < 1) { if (isset($outbox['totalItems']) && $outbox['totalItems'] < 1) {
// Skip remote fetch, not enough posts // Skip remote fetch, not enough posts
Log::info('not enough items'); Log::info('not enough items');
return; return;
} }
if(isset($outbox['type']) && in_array($outbox['type'], $types)) { if (isset($outbox['type']) && in_array($outbox['type'], $types)) {
Log::info('handle ordered collection'); Log::info('handle ordered collection');
$this->handleOrderedCollection(); $this->handleOrderedCollection();
} }
@ -89,19 +92,20 @@ class RemoteFollowImportRecent implements ShouldQueue
{ {
$outbox = $this->outbox; $outbox = $this->outbox;
if(!isset($outbox['next']) && !isset($outbox['first']['next']) && $this->cursor !== 1) { if (!isset($outbox['next']) && !isset($outbox['first']['next']) && $this->cursor !== 1) {
$this->cursor = 40; $this->cursor = 40;
$outbox['next'] = false; $outbox['next'] = false;
} }
if($outbox['type'] == 'OrderedCollectionPage') { if ($outbox['type'] == 'OrderedCollectionPage') {
$this->nextUrl = $outbox['next']; $this->nextUrl = $outbox['next'];
} }
if(isset($outbox['first']) && !is_array($outbox['first'])) { if (isset($outbox['first']) && !is_array($outbox['first'])) {
// Mastodon detected // Mastodon detected
Log::info('Mastodon detected...'); Log::info('Mastodon detected...');
$this->nextUrl = $outbox['first']; $this->nextUrl = $outbox['first'];
return $this->fetchOutbox($this->nextUrl); return $this->fetchOutbox($this->nextUrl);
} else { } else {
// Pleroma detected. // Pleroma detected.
@ -110,33 +114,31 @@ class RemoteFollowImportRecent implements ShouldQueue
$orderedItems = isset($outbox['orderedItems']) ? $outbox['orderedItems'] : $outbox['first']['orderedItems']; $orderedItems = isset($outbox['orderedItems']) ? $outbox['orderedItems'] : $outbox['first']['orderedItems'];
} }
foreach ($orderedItems as $item) {
foreach($orderedItems as $item) {
Log::info('Parsing items...'); Log::info('Parsing items...');
$parsed = $this->parseObject($item); $parsed = $this->parseObject($item);
if($parsed !== 0) { if ($parsed !== 0) {
Log::info('Found media!'); Log::info('Found media!');
$this->importActivity($item); $this->importActivity($item);
} }
} }
if($this->cursor < 40 && $this->mediaCount < 9) { if ($this->cursor < 40 && $this->mediaCount < 9) {
$this->cursor++; $this->cursor++;
$this->mediaCount++; $this->mediaCount++;
$this->fetchOutbox($this->nextUrl); $this->fetchOutbox($this->nextUrl);
} }
} }
public function parseObject($parsed) public function parseObject($parsed)
{ {
if($parsed['type'] !== 'Create') { if ($parsed['type'] !== 'Create') {
return 0; return 0;
} }
$activity = $parsed['object']; $activity = $parsed['object'];
if(isset($activity['attachment']) && !empty($activity['attachment'])) { if (isset($activity['attachment']) && !empty($activity['attachment'])) {
return $this->detectSupportedMedia($activity['attachment']); return $this->detectSupportedMedia($activity['attachment']);
} }
} }
@ -146,7 +148,7 @@ class RemoteFollowImportRecent implements ShouldQueue
$supported = $this->supported; $supported = $this->supported;
$count = 0; $count = 0;
foreach($attachments as $media) { foreach ($attachments as $media) {
$mime = $media['mediaType']; $mime = $media['mediaType'];
$count = in_array($mime, $supported) ? ($count + 1) : $count; $count = in_array($mime, $supported) ? ($count + 1) : $count;
} }
@ -161,11 +163,11 @@ class RemoteFollowImportRecent implements ShouldQueue
$attachments = $activity['object']['attachment']; $attachments = $activity['object']['attachment'];
$caption = str_limit($activity['object']['content'], 125); $caption = str_limit($activity['object']['content'], 125);
if(Status::whereUrl($activity['id'])->count() !== 0) { if (Status::whereUrl($activity['id'])->count() !== 0) {
return true; return true;
} }
$status = new Status; $status = new Status();
$status->profile_id = $profile->id; $status->profile_id = $profile->id;
$status->url = $activity['id']; $status->url = $activity['id'];
$status->local = false; $status->local = false;
@ -174,56 +176,56 @@ class RemoteFollowImportRecent implements ShouldQueue
$count = 0; $count = 0;
foreach($attachments as $media) { foreach ($attachments as $media) {
Log::info($media['mediaType'] . ' - ' . $media['url']); Log::info($media['mediaType'].' - '.$media['url']);
$url = $media['url']; $url = $media['url'];
$mime = $media['mediaType']; $mime = $media['mediaType'];
if(!in_array($mime, $supported)) { if (!in_array($mime, $supported)) {
Log::info('Invalid media, skipping. ' . $mime); Log::info('Invalid media, skipping. '.$mime);
continue; continue;
} }
$count++; $count++;
if($count === 1) { if ($count === 1) {
$status->save(); $status->save();
} }
$this->importMedia($url, $mime, $status); $this->importMedia($url, $mime, $status);
} }
Log::info(count($attachments) . ' media found...'); Log::info(count($attachments).' media found...');
if($count !== 0) { if ($count !== 0) {
NewStatusPipeline::dispatch($status, $status->media->first()); NewStatusPipeline::dispatch($status, $status->media->first());
} }
} }
public function importMedia($url, $mime, $status) public function importMedia($url, $mime, $status)
{ {
$user = $this->profile; $user = $this->profile;
$monthHash = hash('sha1', date('Y') . date('m')); $monthHash = hash('sha1', date('Y').date('m'));
$userHash = hash('sha1', $user->id . (string) $user->created_at); $userHash = hash('sha1', $user->id.(string) $user->created_at);
$storagePath = "public/m/{$monthHash}/{$userHash}"; $storagePath = "public/m/{$monthHash}/{$userHash}";
try {
$info = pathinfo($url);
$img = file_get_contents($url);
$file = '/tmp/' . str_random(12) . $info['basename'];
file_put_contents($file, $img);
$path = Storage::putFile($storagePath, new File($file), 'public');
$media = new Media;
$media->status_id = $status->id;
$media->profile_id = $status->profile_id;
$media->user_id = null;
$media->media_path = $path;
$media->size = 0;
$media->mime = $mime;
$media->save();
ImageThumbnail::dispatch($media);
return true;
} catch (Exception $e) {
return false;
}
}
try {
$info = pathinfo($url);
$img = file_get_contents($url);
$file = '/tmp/'.str_random(12).$info['basename'];
file_put_contents($file, $img);
$path = Storage::putFile($storagePath, new File($file), 'public');
$media = new Media();
$media->status_id = $status->id;
$media->profile_id = $status->profile_id;
$media->user_id = null;
$media->media_path = $path;
$media->size = 0;
$media->mime = $mime;
$media->save();
ImageThumbnail::dispatch($media);
return true;
} catch (Exception $e) {
return false;
}
}
} }

View file

@ -2,18 +2,17 @@
namespace App\Jobs\RemoteFollowPipeline; namespace App\Jobs\RemoteFollowPipeline;
use Zttp\Zttp; use App\Jobs\AvatarPipeline\CreateAvatar;
use App\{Profile}; use App\{Profile};
use GuzzleHttp\Client; use GuzzleHttp\Client;
use HttpSignatures\Context; use HttpSignatures\Context;
use HttpSignatures\GuzzleHttpSignatures; use HttpSignatures\GuzzleHttpSignatures;
use App\Jobs\RemoteFollowPipeline\RemoteFollowImportRecent;
use App\Jobs\AvatarPipeline\CreateAvatar;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Zttp\Zttp;
class RemoteFollowPipeline implements ShouldQueue class RemoteFollowPipeline implements ShouldQueue
{ {
@ -44,27 +43,28 @@ class RemoteFollowPipeline implements ShouldQueue
$follower = $this->follower; $follower = $this->follower;
$url = $this->url; $url = $this->url;
if(Profile::whereRemoteUrl($url)->count() !== 0) { if (Profile::whereRemoteUrl($url)->count() !== 0) {
return true; return true;
} }
$this->discover($url); $this->discover($url);
return true; return true;
} }
public function discover($url) public function discover($url)
{ {
$context = new Context([ $context = new Context([
'keys' => ['examplekey' => 'secret-key-here'], 'keys' => ['examplekey' => 'secret-key-here'],
'algorithm' => 'hmac-sha256', 'algorithm' => 'hmac-sha256',
'headers' => ['(request-target)', 'date'], 'headers' => ['(request-target)', 'date'],
]); ]);
$handlerStack = GuzzleHttpSignatures::defaultHandlerFromContext($context); $handlerStack = GuzzleHttpSignatures::defaultHandlerFromContext($context);
$client = new Client(['handler' => $handlerStack]); $client = new Client(['handler' => $handlerStack]);
$response = Zttp::withHeaders([ $response = Zttp::withHeaders([
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'User-Agent' => 'PixelFedBot v0.1 - https://pixelfed.org' 'User-Agent' => 'PixelFedBot v0.1 - https://pixelfed.org',
])->get($url); ])->get($url);
$this->response = $response->json(); $this->response = $response->json();
@ -78,7 +78,7 @@ class RemoteFollowPipeline implements ShouldQueue
$username = $res['preferredUsername']; $username = $res['preferredUsername'];
$remoteUsername = "@{$username}@{$domain}"; $remoteUsername = "@{$username}@{$domain}";
$profile = new Profile; $profile = new Profile();
$profile->user_id = null; $profile->user_id = null;
$profile->domain = $domain; $profile->domain = $domain;
$profile->username = $remoteUsername; $profile->username = $remoteUsername;
@ -98,8 +98,8 @@ class RemoteFollowPipeline implements ShouldQueue
$url = $res['inbox']; $url = $res['inbox'];
$activity = Zttp::withHeaders(['Content-Type' => 'application/activity+json'])->post($url, [ $activity = Zttp::withHeaders(['Content-Type' => 'application/activity+json'])->post($url, [
'type' => 'Follow', 'type' => 'Follow',
'object' => $this->follower->url() 'object' => $this->follower->url(),
]); ]);
} }
} }

View file

@ -2,14 +2,14 @@
namespace App\Jobs\StatusPipeline; namespace App\Jobs\StatusPipeline;
use Cache, Redis; use App\Status;
use App\{Media, Status}; use Cache;
use App\Jobs\ImageOptimizePipeline\ImageOptimize;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Redis;
class NewStatusPipeline implements ShouldQueue class NewStatusPipeline implements ShouldQueue
{ {
@ -39,9 +39,9 @@ class NewStatusPipeline implements ShouldQueue
StatusEntityLexer::dispatch($status); StatusEntityLexer::dispatch($status);
//StatusActivityPubDeliver::dispatch($status); //StatusActivityPubDeliver::dispatch($status);
Cache::forever('post.' . $status->id, $status); Cache::forever('post.'.$status->id, $status);
$redis = Redis::connection(); $redis = Redis::connection();
$redis->lpush(config('cache.prefix').':user.' . $status->profile_id . '.posts', $status->id); $redis->lpush(config('cache.prefix').':user.'.$status->profile_id.'.posts', $status->id);
} }
} }

View file

@ -2,18 +2,19 @@
namespace App\Jobs\StatusPipeline; namespace App\Jobs\StatusPipeline;
use App\{Media, Status}; use App\Status;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class StatusActivityPubDeliver implements ShouldQueue class StatusActivityPubDeliver implements ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $status; protected $status;
/** /**
* Create a new job instance. * Create a new job instance.
* *

View file

@ -2,12 +2,14 @@
namespace App\Jobs\StatusPipeline; namespace App\Jobs\StatusPipeline;
use App\{Media, Notification, StatusHashtag, Status}; use App\Notification;
use App\Status;
use App\StatusHashtag;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class StatusDelete implements ShouldQueue class StatusDelete implements ShouldQueue
{ {
@ -38,28 +40,27 @@ class StatusDelete implements ShouldQueue
public function unlinkRemoveMedia($status) public function unlinkRemoveMedia($status)
{ {
if($status->media()->count() == 0) { if ($status->media()->count() == 0) {
return; return;
} }
foreach($status->media as $media) { foreach ($status->media as $media) {
$thumbnail = storage_path("app/{$media->thumbnail_path}"); $thumbnail = storage_path("app/{$media->thumbnail_path}");
$photo = storage_path("app/{$media->media_path}"); $photo = storage_path("app/{$media->media_path}");
try { try {
if(is_file($thumbnail)) { if (is_file($thumbnail)) {
unlink($thumbnail); unlink($thumbnail);
} }
if(is_file($photo)) { if (is_file($photo)) {
unlink($photo); unlink($photo);
} }
$media->delete(); $media->delete();
} catch (Exception $e) { } catch (Exception $e) {
} }
} }
$comments = Status::where('in_reply_to_id', $status->id)->get(); $comments = Status::where('in_reply_to_id', $status->id)->get();
foreach($comments as $comment) { foreach ($comments as $comment) {
$comment->in_reply_to_id = null; $comment->in_reply_to_id = null;
$comment->save(); $comment->save();
Notification::whereItemType('App\Status') Notification::whereItemType('App\Status')

View file

@ -2,23 +2,20 @@
namespace App\Jobs\StatusPipeline; namespace App\Jobs\StatusPipeline;
use DB, Cache; use App\Hashtag;
use App\{ use App\Jobs\MentionPipeline\MentionPipeline;
Hashtag, use App\Mention;
Media, use App\Profile;
Mention, use App\Status;
Profile, use App\StatusHashtag;
Status, use App\Util\Lexer\Autolink;
StatusHashtag use App\Util\Lexer\Extractor;
}; use DB;
use App\Util\Lexer\Hashtag as HashtagLexer;
use App\Util\Lexer\{Autolink, Extractor};
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use App\Jobs\MentionPipeline\MentionPipeline; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class StatusEntityLexer implements ShouldQueue class StatusEntityLexer implements ShouldQueue
{ {
@ -57,7 +54,7 @@ class StatusEntityLexer implements ShouldQueue
public function extractEntities() public function extractEntities()
{ {
$this->entities = Extractor::create()->extract($this->status->caption); $this->entities = Extractor::create()->extract($this->status->caption);
$this->autolinkStatus(); $this->autolinkStatus();
} }
public function autolinkStatus() public function autolinkStatus()
@ -83,7 +80,7 @@ class StatusEntityLexer implements ShouldQueue
$tags = array_unique($this->entities['hashtags']); $tags = array_unique($this->entities['hashtags']);
$status = $this->status; $status = $this->status;
foreach($tags as $tag) { foreach ($tags as $tag) {
DB::transaction(function () use ($status, $tag) { DB::transaction(function () use ($status, $tag) {
$slug = str_slug($tag); $slug = str_slug($tag);
$hashtag = Hashtag::firstOrCreate( $hashtag = Hashtag::firstOrCreate(
@ -101,22 +98,21 @@ class StatusEntityLexer implements ShouldQueue
$mentions = array_unique($this->entities['mentions']); $mentions = array_unique($this->entities['mentions']);
$status = $this->status; $status = $this->status;
foreach($mentions as $mention) { foreach ($mentions as $mention) {
$mentioned = Profile::whereUsername($mention)->firstOrFail(); $mentioned = Profile::whereUsername($mention)->firstOrFail();
if(empty($mentioned) || !isset($mentioned->id)) { if (empty($mentioned) || !isset($mentioned->id)) {
continue; continue;
} }
DB::transaction(function () use ($status, $mentioned) { DB::transaction(function () use ($status, $mentioned) {
$m = new Mention; $m = new Mention();
$m->status_id = $status->id; $m->status_id = $status->id;
$m->profile_id = $mentioned->id; $m->profile_id = $mentioned->id;
$m->save(); $m->save();
MentionPipeline::dispatch($status, $m); MentionPipeline::dispatch($status, $m);
}); });
} }
} }
} }

View file

@ -18,25 +18,27 @@ class Like extends Model
public function actor() public function actor()
{ {
return $this->belongsTo(Profile::class, 'profile_id', 'id'); return $this->belongsTo(Profile::class, 'profile_id', 'id');
} }
public function status() public function status()
{ {
return $this->belongsTo(Status::class); return $this->belongsTo(Status::class);
} }
public function toText() public function toText()
{ {
$actorName = $this->actor->username; $actorName = $this->actor->username;
return "{$actorName} " . __('notification.likedPhoto');
return "{$actorName} ".__('notification.likedPhoto');
} }
public function toHtml() public function toHtml()
{ {
$actorName = $this->actor->username; $actorName = $this->actor->username;
$actorUrl = $this->actor->url(); $actorUrl = $this->actor->url();
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> " .
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> ".
__('notification.likedPhoto'); __('notification.likedPhoto');
} }
} }

View file

@ -6,7 +6,6 @@ use App\EmailVerification;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class ConfirmEmail extends Mailable class ConfirmEmail extends Mailable
{ {

View file

@ -2,9 +2,9 @@
namespace App; namespace App;
use Storage;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Storage;
class Media extends Model class Media extends Model
{ {
@ -16,18 +16,20 @@ class Media extends Model
* @var array * @var array
*/ */
protected $dates = ['deleted_at']; protected $dates = ['deleted_at'];
public function url() public function url()
{ {
$path = $this->media_path; $path = $this->media_path;
$url = Storage::url($path); $url = Storage::url($path);
return url($url);
return url($url);
} }
public function thumbnailUrl() public function thumbnailUrl()
{ {
$path = $this->thumbnail_path; $path = $this->thumbnail_path;
$url = Storage::url($path); $url = Storage::url($path);
return url($url);
return url($url);
} }
} }

View file

@ -18,25 +18,27 @@ class Mention extends Model
public function profile() public function profile()
{ {
return $this->belongsTo(Profile::class, 'profile_id', 'id'); return $this->belongsTo(Profile::class, 'profile_id', 'id');
} }
public function status() public function status()
{ {
return $this->belongsTo(Status::class, 'status_id', 'id'); return $this->belongsTo(Status::class, 'status_id', 'id');
} }
public function toText() public function toText()
{ {
$actorName = $this->status->profile->username; $actorName = $this->status->profile->username;
return "{$actorName} " . __('notification.mentionedYou');
return "{$actorName} ".__('notification.mentionedYou');
} }
public function toHtml() public function toHtml()
{ {
$actorName = $this->status->profile->username; $actorName = $this->status->profile->username;
$actorUrl = $this->status->profile->url(); $actorUrl = $this->status->profile->url();
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> " .
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> ".
__('notification.mentionedYou'); __('notification.mentionedYou');
} }
} }

View file

@ -15,25 +15,24 @@ class Notification extends Model
* @var array * @var array
*/ */
protected $dates = ['deleted_at']; protected $dates = ['deleted_at'];
public function actor() public function actor()
{ {
return $this->belongsTo(Profile::class, 'actor_id', 'id'); return $this->belongsTo(Profile::class, 'actor_id', 'id');
} }
public function profile() public function profile()
{ {
return $this->belongsTo(Profile::class, 'profile_id', 'id'); return $this->belongsTo(Profile::class, 'profile_id', 'id');
} }
public function item() public function item()
{ {
return $this->morphTo(); return $this->morphTo();
} }
public function status() public function status()
{ {
return $this->belongsTo(Status::class, 'item_id', 'id'); return $this->belongsTo(Status::class, 'item_id', 'id');
} }
} }

View file

@ -2,28 +2,31 @@
namespace App\Observers; namespace App\Observers;
use App\{Profile, User, UserSetting};
use App\Jobs\AvatarPipeline\CreateAvatar; use App\Jobs\AvatarPipeline\CreateAvatar;
use App\Profile;
use App\User;
use App\UserSetting;
class UserObserver class UserObserver
{ {
/** /**
* Listen to the User created event. * Listen to the User created event.
* *
* @param \App\User $user * @param \App\User $user
*
* @return void * @return void
*/ */
public function saved(User $user) public function saved(User $user)
{ {
if(empty($user->profile)) { if (empty($user->profile)) {
$profile = new Profile; $profile = new Profile();
$profile->user_id = $user->id; $profile->user_id = $user->id;
$profile->username = $user->username; $profile->username = $user->username;
$profile->name = $user->name; $profile->name = $user->name;
$pkiConfig = [ $pkiConfig = [
"digest_alg" => "sha512", 'digest_alg' => 'sha512',
"private_key_bits" => 2048, 'private_key_bits' => 2048,
"private_key_type" => OPENSSL_KEYTYPE_RSA, 'private_key_type' => OPENSSL_KEYTYPE_RSA,
]; ];
$pki = openssl_pkey_new($pkiConfig); $pki = openssl_pkey_new($pkiConfig);
openssl_pkey_export($pki, $pki_private); openssl_pkey_export($pki, $pki_private);
@ -37,11 +40,10 @@ class UserObserver
CreateAvatar::dispatch($profile); CreateAvatar::dispatch($profile);
} }
if(empty($user->settings)) { if (empty($user->settings)) {
$settings = new UserSetting; $settings = new UserSetting();
$settings->user_id = $user->id; $settings->user_id = $user->id;
$settings->save(); $settings->save();
} }
} }
}
}

View file

@ -2,10 +2,12 @@
namespace App; namespace App;
use Auth, Cache, Storage;
use App\Util\Lexer\PrettyNumber; use App\Util\Lexer\PrettyNumber;
use Auth;
use Cache;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Storage;
class Profile extends Model class Profile extends Model
{ {
@ -30,27 +32,28 @@ class Profile extends Model
public function url($suffix = '') public function url($suffix = '')
{ {
if($this->remote_url) { if ($this->remote_url) {
return $this->remote_url; return $this->remote_url;
} else { } else {
return url($this->username . $suffix); return url($this->username.$suffix);
} }
} }
public function localUrl($suffix = '') public function localUrl($suffix = '')
{ {
return url($this->username . $suffix); return url($this->username.$suffix);
} }
public function permalink($suffix = '') public function permalink($suffix = '')
{ {
return url('users/' . $this->username . $suffix); return url('users/'.$this->username.$suffix);
} }
public function emailUrl() public function emailUrl()
{ {
$domain = parse_url(config('app.url'), PHP_URL_HOST); $domain = parse_url(config('app.url'), PHP_URL_HOST);
return $this->username . '@' . $domain;
return $this->username.'@'.$domain;
} }
public function statuses() public function statuses()
@ -61,7 +64,7 @@ class Profile extends Model
public function followingCount($short = false) public function followingCount($short = false)
{ {
$count = $this->following()->count(); $count = $this->following()->count();
if($short) { if ($short) {
return PrettyNumber::convert($count); return PrettyNumber::convert($count);
} else { } else {
return $count; return $count;
@ -71,7 +74,7 @@ class Profile extends Model
public function followerCount($short = false) public function followerCount($short = false)
{ {
$count = $this->followers()->count(); $count = $this->followers()->count();
if($short) { if ($short) {
return PrettyNumber::convert($count); return PrettyNumber::convert($count);
} else { } else {
return $count; return $count;
@ -81,7 +84,7 @@ class Profile extends Model
public function following() public function following()
{ {
return $this->belongsToMany( return $this->belongsToMany(
Profile::class, self::class,
'followers', 'followers',
'profile_id', 'profile_id',
'following_id' 'following_id'
@ -91,7 +94,7 @@ class Profile extends Model
public function followers() public function followers()
{ {
return $this->belongsToMany( return $this->belongsToMany(
Profile::class, self::class,
'followers', 'followers',
'following_id', 'following_id',
'profile_id' 'profile_id'
@ -126,18 +129,20 @@ class Profile extends Model
public function avatar() public function avatar()
{ {
return $this->hasOne(Avatar::class)->withDefault([ return $this->hasOne(Avatar::class)->withDefault([
'media_path' => 'public/avatars/default.png' 'media_path' => 'public/avatars/default.png',
]); ]);
} }
public function avatarUrl() public function avatarUrl()
{ {
$url = Cache::remember("avatar:{$this->id}", 1440, function() { $url = Cache::remember("avatar:{$this->id}", 1440, function () {
$path = optional($this->avatar)->media_path; $path = optional($this->avatar)->media_path;
$version = hash('sha1', $this->avatar->created_at); $version = hash('sha1', $this->avatar->created_at);
$path = "{$path}?v={$version}"; $path = "{$path}?v={$version}";
return url(Storage::url($path)); return url(Storage::url($path));
}); });
return $url; return $url;
} }
@ -167,8 +172,8 @@ class Profile extends Model
->limit(3) ->limit(3)
->pluck('following_id'); ->pluck('following_id');
$recommended = []; $recommended = [];
foreach($following as $follow) { foreach ($following as $follow) {
$recommended[] = Profile::findOrFail($follow); $recommended[] = self::findOrFail($follow);
} }
return $recommended; return $recommended;
@ -176,9 +181,10 @@ class Profile extends Model
public function keyId() public function keyId()
{ {
if($this->remote_url) { if ($this->remote_url) {
return; return;
} }
return $this->permalink('#main-key'); return $this->permalink('#main-key');
} }
} }

View file

@ -2,9 +2,10 @@
namespace App\Providers; namespace App\Providers;
use App\User;
use Auth, Horizon;
use App\Observers\UserObserver; use App\Observers\UserObserver;
use App\User;
use Auth;
use Horizon;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
@ -26,34 +27,39 @@ class AppServiceProvider extends ServiceProvider
return Auth::check() && $request->user()->is_admin; return Auth::check() && $request->user()->is_admin;
}); });
Blade::directive('prettyNumber', function($expression) { Blade::directive('prettyNumber', function ($expression) {
$num = $expression; $num = $expression;
$abbrevs = array(12 => "T", 9 => "B", 6 => "M", 3 => "K", 0 => ""); $abbrevs = [12 => 'T', 9 => 'B', 6 => 'M', 3 => 'K', 0 => ''];
foreach($abbrevs as $exponent => $abbrev) { foreach ($abbrevs as $exponent => $abbrev) {
if($expression >= pow(10, $exponent)) { if ($expression >= pow(10, $exponent)) {
$display_num = $expression / pow(10, $exponent); $display_num = $expression / pow(10, $exponent);
$num = number_format($display_num,0) . $abbrev; $num = number_format($display_num, 0).$abbrev;
return "<?php echo '$num'; ?>";
return "<?php echo '$num'; ?>";
} }
} }
return "<?php echo $num; ?>"; return "<?php echo $num; ?>";
}); });
Blade::directive('prettySize', function($expression) { Blade::directive('prettySize', function ($expression) {
$size = intval($expression); $size = intval($expression);
$precision = 0; $precision = 0;
$short = true; $short = true;
$units = $short ? $units = $short ?
['B','k','M','G','T','P','E','Z','Y'] : ['B', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] :
['B','kB','MB','GB','TB','PB','EB','ZB','YB']; ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
for($i = 0; ($size / 1024) > 0.9; $i++, $size /= 1024) {} for ($i = 0; ($size / 1024) > 0.9; $i++, $size /= 1024) {
}
$res = round($size, $precision).$units[$i]; $res = round($size, $precision).$units[$i];
return "<?php echo '$res'; ?>"; return "<?php echo '$res'; ?>";
}); });
Blade::directive('maxFileSize', function() { Blade::directive('maxFileSize', function () {
$value = config('pixelfed.max_photo_size'); $value = config('pixelfed.max_photo_size');
return \App\Util\Lexer\PrettyNumber::size($value, true);
return \App\Util\Lexer\PrettyNumber::size($value, true);
}); });
} }

View file

@ -2,7 +2,6 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider class AuthServiceProvider extends ServiceProvider

View file

@ -2,8 +2,8 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Broadcast; use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider class BroadcastServiceProvider extends ServiceProvider
{ {

View file

@ -2,8 +2,8 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider class EventServiceProvider extends ServiceProvider
{ {

View file

@ -2,8 +2,8 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider class RouteServiceProvider extends ServiceProvider
{ {

View file

@ -7,34 +7,35 @@ use Illuminate\Database\Eloquent\Model;
class Report extends Model class Report extends Model
{ {
protected $dates = ['admin_seen']; protected $dates = ['admin_seen'];
public function url() public function url()
{ {
return url('/i/admin/reports/show/' . $this->id); return url('/i/admin/reports/show/'.$this->id);
} }
public function reporter() public function reporter()
{ {
return $this->belongsTo(Profile::class, 'profile_id'); return $this->belongsTo(Profile::class, 'profile_id');
} }
public function reported() public function reported()
{ {
$class = $this->object_type; $class = $this->object_type;
switch ($class) { switch ($class) {
case 'App\Status': case 'App\Status':
$column = 'id'; $column = 'id';
break; break;
default: default:
$column = 'id'; $column = 'id';
break; break;
} }
return (new $class())->where($column, $this->object_id)->firstOrFail();
return (new $class())->where($column, $this->object_id)->firstOrFail();
} }
public function reportedUser() public function reportedUser()
{ {
return $this->belongsTo(Profile::class, 'reported_profile_id', 'id'); return $this->belongsTo(Profile::class, 'reported_profile_id', 'id');
} }
} }

View file

@ -2,9 +2,10 @@
namespace App; namespace App;
use Auth, Storage; use Auth;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Storage;
class Status extends Model class Status extends Model
{ {
@ -16,125 +17,133 @@ class Status extends Model
* @var array * @var array
*/ */
protected $dates = ['deleted_at']; protected $dates = ['deleted_at'];
public function profile() public function profile()
{ {
return $this->belongsTo(Profile::class); return $this->belongsTo(Profile::class);
} }
public function media() public function media()
{ {
return $this->hasMany(Media::class); return $this->hasMany(Media::class);
} }
public function firstMedia() public function firstMedia()
{ {
return $this->hasMany(Media::class)->orderBy('order', 'asc')->first(); return $this->hasMany(Media::class)->orderBy('order', 'asc')->first();
} }
public function viewType() public function viewType()
{ {
$media = $this->firstMedia(); $media = $this->firstMedia();
$type = explode('/', $media->mime); $type = explode('/', $media->mime);
return $type[0];
return $type[0];
} }
public function thumb($showNsfw = false) public function thumb($showNsfw = false)
{ {
$type = $this->viewType(); $type = $this->viewType();
$is_nsfw = !$showNsfw ? $this->is_nsfw : false; $is_nsfw = !$showNsfw ? $this->is_nsfw : false;
if($this->media->count() == 0 || $is_nsfw || $type != 'image') { if ($this->media->count() == 0 || $is_nsfw || $type != 'image') {
return "data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=="; return 'data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==';
} }
return url(Storage::url($this->firstMedia()->thumbnail_path));
return url(Storage::url($this->firstMedia()->thumbnail_path));
} }
public function url() public function url()
{ {
$id = $this->id; $id = $this->id;
$username = $this->profile->username; $username = $this->profile->username;
$path = config('app.url') . "/p/{$username}/{$id}"; $path = config('app.url')."/p/{$username}/{$id}";
if(!is_null($this->in_reply_to_id)) { if (!is_null($this->in_reply_to_id)) {
$pid = $this->in_reply_to_id; $pid = $this->in_reply_to_id;
$path = config('app.url') . "/p/{$username}/{$pid}/c/{$id}"; $path = config('app.url')."/p/{$username}/{$pid}/c/{$id}";
} }
return url($path);
return url($path);
} }
public function permalink($suffix = '/activity') public function permalink($suffix = '/activity')
{ {
$id = $this->id; $id = $this->id;
$username = $this->profile->username; $username = $this->profile->username;
$path = config('app.url') . "/p/{$username}/{$id}{$suffix}"; $path = config('app.url')."/p/{$username}/{$id}{$suffix}";
return url($path);
return url($path);
} }
public function editUrl() public function editUrl()
{ {
return $this->url() . '/edit'; return $this->url().'/edit';
} }
public function mediaUrl() public function mediaUrl()
{ {
$media = $this->firstMedia(); $media = $this->firstMedia();
$path = $media->media_path; $path = $media->media_path;
$hash = is_null($media->processed_at) ? md5('unprocessed') : md5($media->created_at); $hash = is_null($media->processed_at) ? md5('unprocessed') : md5($media->created_at);
$url = Storage::url($path) . "?v={$hash}"; $url = Storage::url($path)."?v={$hash}";
return url($url);
return url($url);
} }
public function likes() public function likes()
{ {
return $this->hasMany(Like::class); return $this->hasMany(Like::class);
} }
public function liked() : bool public function liked() : bool
{ {
$profile = Auth::user()->profile; $profile = Auth::user()->profile;
return Like::whereProfileId($profile->id)->whereStatusId($this->id)->count();
return Like::whereProfileId($profile->id)->whereStatusId($this->id)->count();
} }
public function comments() public function comments()
{ {
return $this->hasMany(Status::class, 'in_reply_to_id'); return $this->hasMany(self::class, 'in_reply_to_id');
} }
public function bookmarked() public function bookmarked()
{ {
if(!Auth::check()) { if (!Auth::check()) {
return 0; return 0;
} }
$profile = Auth::user()->profile; $profile = Auth::user()->profile;
return Bookmark::whereProfileId($profile->id)->whereStatusId($this->id)->count();
return Bookmark::whereProfileId($profile->id)->whereStatusId($this->id)->count();
} }
public function shares() public function shares()
{ {
return $this->hasMany(Status::class, 'reblog_of_id'); return $this->hasMany(self::class, 'reblog_of_id');
} }
public function shared() : bool public function shared() : bool
{ {
$profile = Auth::user()->profile; $profile = Auth::user()->profile;
return Status::whereProfileId($profile->id)->whereReblogOfId($this->id)->count();
return self::whereProfileId($profile->id)->whereReblogOfId($this->id)->count();
} }
public function parent() public function parent()
{ {
$parent = $this->in_reply_to_id ?? $this->reblog_of_id; $parent = $this->in_reply_to_id ?? $this->reblog_of_id;
if(!empty($parent)) { if (!empty($parent)) {
return Status::findOrFail($parent); return self::findOrFail($parent);
} }
} }
public function conversation() public function conversation()
{ {
return $this->hasOne(Conversation::class); return $this->hasOne(Conversation::class);
} }
public function hashtags() public function hashtags()
{ {
return $this->hasManyThrough( return $this->hasManyThrough(
Hashtag::class, Hashtag::class,
StatusHashtag::class, StatusHashtag::class,
'status_id', 'status_id',
@ -146,7 +155,7 @@ class Status extends Model
public function mentions() public function mentions()
{ {
return $this->hasManyThrough( return $this->hasManyThrough(
Profile::class, Profile::class,
Mention::class, Mention::class,
'status_id', 'status_id',
@ -158,45 +167,48 @@ class Status extends Model
public function reportUrl() public function reportUrl()
{ {
return route('report.form') . "?type=post&id={$this->id}"; return route('report.form')."?type=post&id={$this->id}";
} }
public function toActivityStream() public function toActivityStream()
{ {
$media = $this->media; $media = $this->media;
$mediaCollection = []; $mediaCollection = [];
foreach($media as $image) { foreach ($media as $image) {
$mediaCollection[] = [ $mediaCollection[] = [
"type" => "Link", 'type' => 'Link',
"href" => $image->url(), 'href' => $image->url(),
"mediaType" => $image->mime 'mediaType' => $image->mime,
]; ];
} }
$obj = [ $obj = [
"@context" => "https://www.w3.org/ns/activitystreams", '@context' => 'https://www.w3.org/ns/activitystreams',
"type" => "Image", 'type' => 'Image',
"name" => null, 'name' => null,
"url" => $mediaCollection 'url' => $mediaCollection,
]; ];
return $obj;
return $obj;
} }
public function replyToText() public function replyToText()
{ {
$actorName = $this->profile->username; $actorName = $this->profile->username;
return "{$actorName} " . __('notification.commented');
return "{$actorName} ".__('notification.commented');
} }
public function replyToHtml() public function replyToHtml()
{ {
$actorName = $this->profile->username; $actorName = $this->profile->username;
$actorUrl = $this->profile->url(); $actorUrl = $this->profile->url();
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> " .
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> ".
__('notification.commented'); __('notification.commented');
} }
public function recentComments() public function recentComments()
{ {
return $this->comments()->orderBy('created_at','desc')->take(3); return $this->comments()->orderBy('created_at', 'desc')->take(3);
} }
} }

View file

@ -7,19 +7,18 @@ use League\Fractal;
class ProfileOutbox extends Fractal\TransformerAbstract class ProfileOutbox extends Fractal\TransformerAbstract
{ {
public function transform(Profile $profile)
public function transform(Profile $profile) {
{ $count = $profile->statuses()->count();
$count = $profile->statuses()->count(); $statuses = $profile->statuses()->has('media')->orderBy('id', 'desc')->take(20)->get()->map(function ($i, $k) {
$statuses = $profile->statuses()->has('media')->orderBy('id','desc')->take(20)->get()->map(function($i, $k) { $item = [
$item = [
'id' => $i->permalink(), 'id' => $i->permalink(),
// TODO: handle other types // TODO: handle other types
'type' => 'Create', 'type' => 'Create',
'actor' => $i->profile->url(), 'actor' => $i->profile->url(),
'published' => $i->created_at->toISO8601String(), 'published' => $i->created_at->toISO8601String(),
'to' => [ 'to' => [
'https://www.w3.org/ns/activitystreams#Public' 'https://www.w3.org/ns/activitystreams#Public',
], ],
'cc' => [ 'cc' => [
$i->profile->permalink('/followers'), $i->profile->permalink('/followers'),
@ -31,48 +30,48 @@ class ProfileOutbox extends Fractal\TransformerAbstract
'type' => 'Note', 'type' => 'Note',
// XXX: CW Title // XXX: CW Title
'summary' => null, 'summary' => null,
'content' => $i->rendered ?? $i->caption, 'content' => $i->rendered ?? $i->caption,
'inReplyTo' => null, 'inReplyTo' => null,
// TODO: fix date format // TODO: fix date format
'published' => $i->created_at->toAtomString(), 'published' => $i->created_at->toAtomString(),
'url' => $i->url(), 'url' => $i->url(),
'attributedTo' => $i->profile->permalink(), 'attributedTo' => $i->profile->permalink(),
'to' => [ 'to' => [
// TODO: handle proper scope // TODO: handle proper scope
'https://www.w3.org/ns/activitystreams#Public' 'https://www.w3.org/ns/activitystreams#Public',
], ],
'cc' => [ 'cc' => [
// TODO: add cc's // TODO: add cc's
//"{$notice->getProfile()->getUrl()}/subscribers", //"{$notice->getProfile()->getUrl()}/subscribers",
], ],
'sensitive' => (bool) $i->is_nsfw, 'sensitive' => (bool) $i->is_nsfw,
'atomUri' => $i->url(), 'atomUri' => $i->url(),
'inReplyToAtomUri' => null, 'inReplyToAtomUri' => null,
'attachment' => [ 'attachment' => [
// TODO: support more than 1 attachment // TODO: support more than 1 attachment
[ [
'type' => 'Document', 'type' => 'Document',
'mediaType' => $i->firstMedia()->mime, 'mediaType' => $i->firstMedia()->mime,
'url' => $i->firstMedia()->url(), 'url' => $i->firstMedia()->url(),
'name' => null 'name' => null,
] ],
], ],
'tag' => [] 'tag' => [],
] ],
]; ];
return $item;
});
return [ return $item;
'@context' => 'https://www.w3.org/ns/activitystreams', });
'id' => $profile->permalink('/outbox'),
'type' => 'OrderedCollection', return [
'totalItems' => $count, '@context' => 'https://www.w3.org/ns/activitystreams',
'orderedItems' => $statuses 'id' => $profile->permalink('/outbox'),
'type' => 'OrderedCollection',
'totalItems' => $count,
'orderedItems' => $statuses,
]; ];
} }
}
}

View file

@ -7,48 +7,46 @@ use League\Fractal;
class ProfileTransformer extends Fractal\TransformerAbstract class ProfileTransformer extends Fractal\TransformerAbstract
{ {
public function transform(Profile $profile)
public function transform(Profile $profile) {
{ return [
return [
'@context' => [ '@context' => [
'https://www.w3.org/ns/activitystreams', 'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1', 'https://w3id.org/security/v1',
[ [
"manuallyApprovesFollowers" => "as:manuallyApprovesFollowers", 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
"featured" => [ 'featured' => [
"https://pixelfed.org/ns#featured" => ["@type" => "@id"], 'https://pixelfed.org/ns#featured' => ['@type' => '@id'],
] ],
] ],
], ],
'id' => $profile->permalink(), 'id' => $profile->permalink(),
'type' => 'Person', 'type' => 'Person',
'following' => $profile->permalink('/following'), 'following' => $profile->permalink('/following'),
'followers' => $profile->permalink('/followers'), 'followers' => $profile->permalink('/followers'),
'inbox' => $profile->permalink('/inbox'), 'inbox' => $profile->permalink('/inbox'),
'outbox' => $profile->permalink('/outbox'), 'outbox' => $profile->permalink('/outbox'),
'featured' => $profile->permalink('/collections/featured'), 'featured' => $profile->permalink('/collections/featured'),
'preferredUsername' => $profile->username, 'preferredUsername' => $profile->username,
'name' => $profile->name, 'name' => $profile->name,
'summary' => $profile->bio, 'summary' => $profile->bio,
'url' => $profile->url(), 'url' => $profile->url(),
'manuallyApprovesFollowers' => (bool) $profile->is_private, 'manuallyApprovesFollowers' => (bool) $profile->is_private,
// 'follower_count' => $profile->followers()->count(), // 'follower_count' => $profile->followers()->count(),
// 'following_count' => $profile->following()->count(), // 'following_count' => $profile->following()->count(),
'publicKey' => [ 'publicKey' => [
'id' => $profile->permalink() . '#main-key', 'id' => $profile->permalink().'#main-key',
'owner' => $profile->permalink(), 'owner' => $profile->permalink(),
'publicKeyPem' => $profile->public_key 'publicKeyPem' => $profile->public_key,
], ],
'endpoints' => [ 'endpoints' => [
'sharedInbox' => config('routes.api.sharedInbox') 'sharedInbox' => config('routes.api.sharedInbox'),
], ],
'icon' => [ 'icon' => [
'type' => 'Image', 'type' => 'Image',
'mediaType' => 'image/jpeg', 'mediaType' => 'image/jpeg',
'url' => $profile->avatarUrl() 'url' => $profile->avatarUrl(),
] ],
]; ];
} }
}
}

View file

@ -2,24 +2,23 @@
namespace App\Transformer\ActivityPub; namespace App\Transformer\ActivityPub;
use App\{Profile, Status}; use App\Status;
use League\Fractal; use League\Fractal;
class StatusTransformer extends Fractal\TransformerAbstract class StatusTransformer extends Fractal\TransformerAbstract
{ {
public function transform(Status $status)
public function transform(Status $status) {
{ return [
return [
'@context' => [ '@context' => [
'https://www.w3.org/ns/activitystreams', 'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1', 'https://w3id.org/security/v1',
[ [
"manuallyApprovesFollowers" => "as:manuallyApprovesFollowers", 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
"featured" => [ 'featured' => [
"https://pixelfed.org/ns#featured" => ["@type" => "@id"], 'https://pixelfed.org/ns#featured' => ['@type' => '@id'],
] ],
] ],
], ],
'id' => $status->url(), 'id' => $status->url(),
@ -27,35 +26,34 @@ class StatusTransformer extends Fractal\TransformerAbstract
'type' => 'Note', 'type' => 'Note',
// XXX: CW Title // XXX: CW Title
'summary' => null, 'summary' => null,
'content' => $status->rendered ?? $status->caption, 'content' => $status->rendered ?? $status->caption,
'inReplyTo' => null, 'inReplyTo' => null,
// TODO: fix date format // TODO: fix date format
'published' => $status->created_at->toAtomString(), 'published' => $status->created_at->toAtomString(),
'url' => $status->url(), 'url' => $status->url(),
'attributedTo' => $status->profile->permalink(), 'attributedTo' => $status->profile->permalink(),
'to' => [ 'to' => [
// TODO: handle proper scope // TODO: handle proper scope
'https://www.w3.org/ns/activitystreams#Public' 'https://www.w3.org/ns/activitystreams#Public',
], ],
'cc' => [ 'cc' => [
// TODO: add cc's // TODO: add cc's
$status->profile->permalink('/followers'), $status->profile->permalink('/followers'),
], ],
'sensitive' => (bool) $status->is_nsfw, 'sensitive' => (bool) $status->is_nsfw,
'atomUri' => $status->url(), 'atomUri' => $status->url(),
'inReplyToAtomUri' => null, 'inReplyToAtomUri' => null,
'attachment' => $status->media->map(function($media) { 'attachment' => $status->media->map(function ($media) {
return [ return [
'type' => 'Document', 'type' => 'Document',
'mediaType' => $media->mime, 'mediaType' => $media->mime,
'url' => $media->url(), 'url' => $media->url(),
'name' => null 'name' => null,
]; ];
}), }),
'tag' => [] 'tag' => [],
]; ];
} }
}
}

View file

@ -7,27 +7,27 @@ use League\Fractal;
class AccountTransformer extends Fractal\TransformerAbstract class AccountTransformer extends Fractal\TransformerAbstract
{ {
public function transform(Profile $profile) public function transform(Profile $profile)
{ {
return [ return [
'id' => $profile->id, 'id' => $profile->id,
'username' => $profile->username, 'username' => $profile->username,
'acct' => $profile->username, 'acct' => $profile->username,
'display_name' => $profile->name, 'display_name' => $profile->name,
'locked' => (bool) $profile->is_private, 'locked' => (bool) $profile->is_private,
'created_at' => $profile->created_at->format('c'), 'created_at' => $profile->created_at->format('c'),
'followers_count' => $profile->followerCount(), 'followers_count' => $profile->followerCount(),
'following_count' => $profile->followingCount(), 'following_count' => $profile->followingCount(),
'statuses_count' => $profile->statusCount(), 'statuses_count' => $profile->statusCount(),
'note' => $profile->bio, 'note' => $profile->bio,
'url' => $profile->url(), 'url' => $profile->url(),
'avatar' => $profile->avatarUrl(), 'avatar' => $profile->avatarUrl(),
'avatar_static' => $profile->avatarUrl(), 'avatar_static' => $profile->avatarUrl(),
'header' => '', 'header' => '',
'header_static' => '', 'header_static' => '',
'moved' => null, 'moved' => null,
'fields' => null, 'fields' => null,
'bot' => null 'bot' => null,
]; ];
} }
} }

View file

@ -6,11 +6,11 @@ use League\Fractal;
class ApplicationTransformer extends Fractal\TransformerAbstract class ApplicationTransformer extends Fractal\TransformerAbstract
{ {
public function transform() public function transform()
{ {
return [ return [
'name' => '', 'name' => '',
'website' => null 'website' => null,
]; ];
} }
} }

View file

@ -4,7 +4,6 @@ namespace App\Transformer\Api;
use App\Hashtag; use App\Hashtag;
use League\Fractal; use League\Fractal;
use League\Fractal\Serializer\ArraySerializer;
class HashtagTransformer extends Fractal\TransformerAbstract class HashtagTransformer extends Fractal\TransformerAbstract
{ {
@ -12,7 +11,7 @@ class HashtagTransformer extends Fractal\TransformerAbstract
{ {
return [ return [
'name' => $hashtag->name, 'name' => $hashtag->name,
'url' => $hashtag->url(), 'url' => $hashtag->url(),
]; ];
} }
} }

View file

@ -4,21 +4,20 @@ namespace App\Transformer\Api;
use App\Media; use App\Media;
use League\Fractal; use League\Fractal;
use League\Fractal\Serializer\ArraySerializer;
class MediaTransformer extends Fractal\TransformerAbstract class MediaTransformer extends Fractal\TransformerAbstract
{ {
public function transform(Media $media) public function transform(Media $media)
{ {
return [ return [
'id' => $media->id, 'id' => $media->id,
'type' => 'image', 'type' => 'image',
'url' => $media->url(), 'url' => $media->url(),
'remote_url' => null, 'remote_url' => null,
'preview_url' => $media->thumbnailUrl(), 'preview_url' => $media->thumbnailUrl(),
'text_url' => null, 'text_url' => null,
'meta' => null, 'meta' => null,
'description' => null 'description' => null,
]; ];
} }
} }

View file

@ -10,10 +10,10 @@ class MentionTransformer extends Fractal\TransformerAbstract
public function transform(Profile $profile) public function transform(Profile $profile)
{ {
return [ return [
'id' => $profile->id, 'id' => $profile->id,
'url' => $profile->url(), 'url' => $profile->url(),
'username' => $profile->username, 'username' => $profile->username,
'acct' => $profile->username, 'acct' => $profile->username,
]; ];
} }
} }

View file

@ -11,59 +11,63 @@ class StatusTransformer extends Fractal\TransformerAbstract
'account', 'account',
'mentions', 'mentions',
'media_attachments', 'media_attachments',
'tags' 'tags',
]; ];
public function transform(Status $status) public function transform(Status $status)
{ {
return [ return [
'id' => $status->id, 'id' => $status->id,
'uri' => $status->url(), 'uri' => $status->url(),
'url' => $status->url(), 'url' => $status->url(),
'in_reply_to_id' => $status->in_reply_to_id, 'in_reply_to_id' => $status->in_reply_to_id,
'in_reply_to_account_id' => $status->in_reply_to_profile_id, 'in_reply_to_account_id' => $status->in_reply_to_profile_id,
// TODO: fixme // TODO: fixme
'reblog' => null, 'reblog' => null,
'content' => "<p>$status->rendered</p>", 'content' => "<p>$status->rendered</p>",
'created_at' => $status->created_at->format('c'), 'created_at' => $status->created_at->format('c'),
'emojis' => [], 'emojis' => [],
'reblogs_count' => $status->shares()->count(), 'reblogs_count' => $status->shares()->count(),
'favourites_count' => $status->likes()->count(), 'favourites_count' => $status->likes()->count(),
'reblogged' => $status->shared(), 'reblogged' => $status->shared(),
'favourited' => $status->liked(), 'favourited' => $status->liked(),
'muted' => null, 'muted' => null,
'sensitive' => (bool) $status->is_nsfw, 'sensitive' => (bool) $status->is_nsfw,
'spoiler_text' => '', 'spoiler_text' => '',
'visibility' => $status->visibility, 'visibility' => $status->visibility,
'application' => null, 'application' => null,
'language' => null, 'language' => null,
'pinned' => null 'pinned' => null,
]; ];
} }
public function includeAccount(Status $status) public function includeAccount(Status $status)
{ {
$account = $status->profile; $account = $status->profile;
return $this->item($account, new AccountTransformer);
return $this->item($account, new AccountTransformer());
} }
public function includeMentions(Status $status) public function includeMentions(Status $status)
{ {
$mentions = $status->mentions; $mentions = $status->mentions;
return $this->collection($mentions, new MentionTransformer);
return $this->collection($mentions, new MentionTransformer());
} }
public function includeMediaAttachments(Status $status) public function includeMediaAttachments(Status $status)
{ {
$media = $status->media; $media = $status->media;
return $this->collection($media, new MediaTransformer);
return $this->collection($media, new MediaTransformer());
} }
public function includeTags(Status $status) public function includeTags(Status $status)
{ {
$tags = $status->hashtags; $tags = $status->hashtags;
return $this->collection($tags, new HashtagTransformer);
return $this->collection($tags, new HashtagTransformer());
} }
} }

View file

@ -2,9 +2,9 @@
namespace App; namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable class User extends Authenticatable
{ {
@ -42,7 +42,7 @@ class User extends Authenticatable
public function url() public function url()
{ {
return url(config('app.url') . '/' . $this->username); return url(config('app.url').'/'.$this->username);
} }
public function settings() public function settings()

View file

@ -7,9 +7,9 @@ use Illuminate\Database\Eloquent\Model;
class UserFilter extends Model class UserFilter extends Model
{ {
protected $fillable = [ protected $fillable = [
'user_id', 'user_id',
'filterable_id', 'filterable_id',
'filterable_type', 'filterable_type',
'filter_type' 'filter_type',
]; ];
} }

View file

@ -4,10 +4,10 @@ namespace App\Util\ActivityPub\Concern;
use Zttp\Zttp; use Zttp\Zttp;
class HTTPSignature { class HTTPSignature
{
protected $localhosts = [ protected $localhosts = [
'127.0.0.1', 'localhost', '::1' '127.0.0.1', 'localhost', '::1',
]; ];
public $profile; public $profile;
public $is_url; public $is_url;
@ -15,20 +15,22 @@ class HTTPSignature {
public function validateUrl() public function validateUrl()
{ {
// If the profile exists, assume its valid // If the profile exists, assume its valid
if($this->is_url === false) { if ($this->is_url === false) {
return true; return true;
} }
$url = $this->profile; $url = $this->profile;
try { try {
$url = filter_var($url, FILTER_VALIDATE_URL); $url = filter_var($url, FILTER_VALIDATE_URL);
$parsed = parse_url($url, PHP_URL_HOST); $parsed = parse_url($url, PHP_URL_HOST);
if(!$parsed || in_array($parsed, $this->localhosts)) { if (!$parsed || in_array($parsed, $this->localhosts)) {
return false; return false;
} }
} catch (Exception $e) { } catch (Exception $e) {
return false; return false;
} }
return true; return true;
} }
@ -37,22 +39,22 @@ class HTTPSignature {
$this->profile = $profile; $this->profile = $profile;
$this->is_url = $is_url; $this->is_url = $is_url;
$valid = $this->validateUrl(); $valid = $this->validateUrl();
if(!$valid) { if (!$valid) {
throw new \Exception('Invalid URL provided'); throw new \Exception('Invalid URL provided');
} }
if($is_url && isset($profile->public_key) && $profile->public_key) { if ($is_url && isset($profile->public_key) && $profile->public_key) {
return $profile->public_key; return $profile->public_key;
} }
try { try {
$url = $this->profile; $url = $this->profile;
$res = Zttp::timeout(30)->withHeaders([ $res = Zttp::timeout(30)->withHeaders([
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'User-Agent' => 'PixelFedBot v0.1 - https://pixelfed.org' 'User-Agent' => 'PixelFedBot v0.1 - https://pixelfed.org',
])->get($url); ])->get($url);
$actor = json_decode($res->getBody(), true); $actor = json_decode($res->getBody(), true);
} catch (Exception $e) { } catch (Exception $e) {
throw new Exception('Unable to fetch public key'); throw new Exception('Unable to fetch public key');
} }
return $actor['publicKey']['publicKeyPem']; return $actor['publicKey']['publicKeyPem'];
@ -62,33 +64,31 @@ class HTTPSignature {
{ {
$profile = $senderProfile; $profile = $senderProfile;
$context = new Context([ $context = new Context([
'keys' => [$profile->keyId() => $profile->private_key], 'keys' => [$profile->keyId() => $profile->private_key],
'algorithm' => 'rsa-sha256', 'algorithm' => 'rsa-sha256',
'headers' => ['(request-target)', 'Date'], 'headers' => ['(request-target)', 'Date'],
]); ]);
$handlerStack = GuzzleHttpSignatures::defaultHandlerFromContext($context); $handlerStack = GuzzleHttpSignatures::defaultHandlerFromContext($context);
$client = new Client(['handler' => $handlerStack]); $client = new Client(['handler' => $handlerStack]);
$headers = [ $headers = [
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'Date' => date('D, d M Y h:i:s') . ' GMT', 'Date' => date('D, d M Y h:i:s').' GMT',
'Content-Type' => 'application/activity+json', 'Content-Type' => 'application/activity+json',
'User-Agent' => 'PixelFedBot - https://pixelfed.org' 'User-Agent' => 'PixelFedBot - https://pixelfed.org',
]; ];
$response = $client->post($url, [ $response = $client->post($url, [
'options' => [ 'options' => [
'allow_redirects' => false, 'allow_redirects' => false,
'verify' => true, 'verify' => true,
'timeout' => 30 'timeout' => 30,
], ],
'headers' => $headers, 'headers' => $headers,
'body' => $body 'body' => $body,
]); ]);
return $response->getBody(); return $response->getBody();
} }
}
}

View file

@ -2,48 +2,48 @@
namespace App\Util\ActivityPub; namespace App\Util\ActivityPub;
use \Zttp\Zttp; use Zttp\Zttp;
class DiscoverActor { class DiscoverActor
{
protected $url;
protected $response;
protected $url; public function __construct($url)
protected $response; {
$this->url = $url;
public function __construct($url)
{
$this->url = $url;
}
public function fetch()
{
$res = Zttp::withHeaders([
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'User-Agent' => 'PixelFedBot - https://pixelfed.org'
])->get($this->url);
$this->response = $res->body();
return $this;
}
public function getResponse()
{
return json_decode($this->response, true);
}
public function getJsonResponse()
{
return $this->response;
}
public function discover()
{
$this->fetch();
$res = $this->getResponse();
if(empty($res) || !in_array('type', $res) || $res['type'] !== 'Person') {
throw new \Exception('Invalid Actor Object');
} }
return $res; public function fetch()
} {
$res = Zttp::withHeaders([
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'User-Agent' => 'PixelFedBot - https://pixelfed.org',
])->get($this->url);
$this->response = $res->body();
} return $this;
}
public function getResponse()
{
return json_decode($this->response, true);
}
public function getJsonResponse()
{
return $this->response;
}
public function discover()
{
$this->fetch();
$res = $this->getResponse();
if (empty($res) || !in_array('type', $res) || $res['type'] !== 'Person') {
throw new \Exception('Invalid Actor Object');
}
return $res;
}
}

View file

@ -2,11 +2,11 @@
namespace App\Util\ActivityPub; namespace App\Util\ActivityPub;
use App\Jobs\AvatarPipeline\CreateAvatar; use App\Like;
use App\{Follower, Like, Profile, Like, Status, User}; use App\Profile;
class Inbox {
class Inbox
{
protected $request; protected $request;
protected $profile; protected $profile;
protected $payload; protected $payload;
@ -58,12 +58,11 @@ class Inbox {
{ {
$actor = $this->payload['object']; $actor = $this->payload['object'];
$target = $this->profile; $target = $this->profile;
} }
public function actorFirstOrCreate($actorUrl) public function actorFirstOrCreate($actorUrl)
{ {
if(Profile::whereRemoteUrl($actorUrl)->count() !== 0) { if (Profile::whereRemoteUrl($actorUrl)->count() !== 0) {
return Profile::whereRemoteUrl($actorUrl)->firstOrFail(); return Profile::whereRemoteUrl($actorUrl)->firstOrFail();
} }
@ -73,7 +72,7 @@ class Inbox {
$username = $res['preferredUsername']; $username = $res['preferredUsername'];
$remoteUsername = "@{$username}@{$domain}"; $remoteUsername = "@{$username}@{$domain}";
$profile = new Profile; $profile = new Profile();
$profile->user_id = null; $profile->user_id = null;
$profile->domain = $domain; $profile->domain = $domain;
$profile->username = $remoteUsername; $profile->username = $remoteUsername;
@ -82,7 +81,5 @@ class Inbox {
$profile->sharedInbox = $res['endpoints']['sharedInbox']; $profile->sharedInbox = $res['endpoints']['sharedInbox'];
$profile->remote_url = $res['url']; $profile->remote_url = $res['url'];
$profile->save(); $profile->save();
} }
}
}

View file

@ -8,14 +8,14 @@ class HashPreprocessor implements \Bitverse\Identicon\Preprocessor\PreprocessorI
public function __construct($algo = 'sha256') public function __construct($algo = 'sha256')
{ {
$this->algo = $algo; $this->algo = $algo;
} }
/** /**
* {@inheritDoc} * {@inheritdoc}
*/ */
public function process($string) public function process($string)
{ {
return hash($this->algo, $string); return hash($this->algo, $string);
} }
} }

View file

@ -5,17 +5,12 @@
* @author Nick Pope <nick@nickpope.me.uk> * @author Nick Pope <nick@nickpope.me.uk>
* @copyright Copyright © 2010, Mike Cochrane, Nick Pope * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter.Text
*/ */
namespace App\Util\Lexer; namespace App\Util\Lexer;
use App\Util\Lexer\Regex;
use App\Util\Lexer\Extractor;
use App\Util\Lexer\StringUtils;
/** /**
* Twitter Autolink Class * Twitter Autolink Class.
* *
* Parses tweets and generates HTML anchor tags around URLs, usernames, * Parses tweets and generates HTML anchor tags around URLs, usernames,
* username/list pairs and hashtags. * username/list pairs and hashtags.
@ -28,85 +23,83 @@ use App\Util\Lexer\StringUtils;
* @author Nick Pope <nick@nickpope.me.uk> * @author Nick Pope <nick@nickpope.me.uk>
* @copyright Copyright © 2010, Mike Cochrane, Nick Pope * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter.Text
*/ */
class Autolink extends Regex class Autolink extends Regex
{ {
/** /**
* CSS class for auto-linked URLs. * CSS class for auto-linked URLs.
* *
* @var string * @var string
*/ */
protected $class_url = ''; protected $class_url = '';
/** /**
* CSS class for auto-linked username URLs. * CSS class for auto-linked username URLs.
* *
* @var string * @var string
*/ */
protected $class_user = 'u-url mention'; protected $class_user = 'u-url mention';
/** /**
* CSS class for auto-linked list URLs. * CSS class for auto-linked list URLs.
* *
* @var string * @var string
*/ */
protected $class_list = 'u-url list-slug'; protected $class_list = 'u-url list-slug';
/** /**
* CSS class for auto-linked hashtag URLs. * CSS class for auto-linked hashtag URLs.
* *
* @var string * @var string
*/ */
protected $class_hash = 'u-url hashtag'; protected $class_hash = 'u-url hashtag';
/** /**
* CSS class for auto-linked cashtag URLs. * CSS class for auto-linked cashtag URLs.
* *
* @var string * @var string
*/ */
protected $class_cash = 'u-url cashtag'; protected $class_cash = 'u-url cashtag';
/** /**
* URL base for username links (the username without the @ will be appended). * URL base for username links (the username without the @ will be appended).
* *
* @var string * @var string
*/ */
protected $url_base_user = null; protected $url_base_user = null;
/** /**
* URL base for list links (the username/list without the @ will be appended). * URL base for list links (the username/list without the @ will be appended).
* *
* @var string * @var string
*/ */
protected $url_base_list = null; protected $url_base_list = null;
/** /**
* URL base for hashtag links (the hashtag without the # will be appended). * URL base for hashtag links (the hashtag without the # will be appended).
* *
* @var string * @var string
*/ */
protected $url_base_hash = null; protected $url_base_hash = null;
/** /**
* URL base for cashtag links (the hashtag without the $ will be appended). * URL base for cashtag links (the hashtag without the $ will be appended).
* *
* @var string * @var string
*/ */
protected $url_base_cash = null; protected $url_base_cash = null;
/** /**
* Whether to include the value 'nofollow' in the 'rel' attribute. * Whether to include the value 'nofollow' in the 'rel' attribute.
* *
* @var bool * @var bool
*/ */
protected $nofollow = true; protected $nofollow = true;
/** /**
* Whether to include the value 'noopener' in the 'rel' attribute. * Whether to include the value 'noopener' in the 'rel' attribute.
* *
* @var bool * @var bool
*/ */
protected $noopener = true; protected $noopener = true;
@ -118,9 +111,9 @@ class Autolink extends Regex
* been undeprecated and thus the 'target' attribute can be used. If this is * been undeprecated and thus the 'target' attribute can be used. If this is
* set to false then the 'target' attribute will be output. * set to false then the 'target' attribute will be output.
* *
* @var bool * @var bool
*/ */
protected $external = true; protected $external = true;
/** /**
* The scope to open the link in. * The scope to open the link in.
@ -129,19 +122,18 @@ class Autolink extends Regex
* since been reinstated in HTML 5. To output the 'target' attribute you * since been reinstated in HTML 5. To output the 'target' attribute you
* must disable the adding of the string 'external' to the 'rel' attribute. * must disable the adding of the string 'external' to the 'rel' attribute.
* *
* @var string * @var string
*/ */
protected $target = '_blank'; protected $target = '_blank';
/** /**
* attribute for invisible span tag * attribute for invisible span tag.
* *
* @var string * @var string
*/ */
protected $invisibleTagAttrs = "style='position:absolute;left:-9999px;'"; protected $invisibleTagAttrs = "style='position:absolute;left:-9999px;'";
/** /**
*
* @var Extractor * @var Extractor
*/ */
protected $extractor = null; protected $extractor = null;
@ -149,12 +141,12 @@ class Autolink extends Regex
/** /**
* Provides fluent method chaining. * Provides fluent method chaining.
* *
* @param string $tweet The tweet to be converted. * @param string $tweet The tweet to be converted.
* @param bool $full_encode Whether to encode all special characters. * @param bool $full_encode Whether to encode all special characters.
* *
* @see __construct() * @see __construct()
* *
* @return Autolink * @return Autolink
*/ */
public static function create($tweet = null, $full_encode = false) public static function create($tweet = null, $full_encode = false)
{ {
@ -169,9 +161,9 @@ class Autolink extends Regex
* *
* @see htmlspecialchars() * @see htmlspecialchars()
* *
* @param string $tweet The tweet to be converted. * @param string $tweet The tweet to be converted.
* @param bool $escape Whether to escape the tweet (default: true). * @param bool $escape Whether to escape the tweet (default: true).
* @param bool $full_encode Whether to encode all special characters. * @param bool $full_encode Whether to encode all special characters.
*/ */
public function __construct($tweet = null, $escape = true, $full_encode = false) public function __construct($tweet = null, $escape = true, $full_encode = false)
{ {
@ -185,16 +177,16 @@ class Autolink extends Regex
parent::__construct($tweet); parent::__construct($tweet);
} }
$this->extractor = Extractor::create(); $this->extractor = Extractor::create();
$this->url_base_user = config('app.url') . '/'; $this->url_base_user = config('app.url').'/';
$this->url_base_list = config('app.url') . '/'; $this->url_base_list = config('app.url').'/';
$this->url_base_hash = config('app.url') . "/discover/tags/"; $this->url_base_hash = config('app.url').'/discover/tags/';
$this->url_base_cash = config('app.url') . '/search?q=%24'; $this->url_base_cash = config('app.url').'/search?q=%24';
} }
/** /**
* CSS class for auto-linked URLs. * CSS class for auto-linked URLs.
* *
* @return string CSS class for URL links. * @return string CSS class for URL links.
*/ */
public function getURLClass() public function getURLClass()
{ {
@ -204,20 +196,21 @@ class Autolink extends Regex
/** /**
* CSS class for auto-linked URLs. * CSS class for auto-linked URLs.
* *
* @param string $v CSS class for URL links. * @param string $v CSS class for URL links.
* *
* @return Autolink Fluid method chaining. * @return Autolink Fluid method chaining.
*/ */
public function setURLClass($v) public function setURLClass($v)
{ {
$this->class_url = trim($v); $this->class_url = trim($v);
return $this; return $this;
} }
/** /**
* CSS class for auto-linked username URLs. * CSS class for auto-linked username URLs.
* *
* @return string CSS class for username links. * @return string CSS class for username links.
*/ */
public function getUsernameClass() public function getUsernameClass()
{ {
@ -227,20 +220,21 @@ class Autolink extends Regex
/** /**
* CSS class for auto-linked username URLs. * CSS class for auto-linked username URLs.
* *
* @param string $v CSS class for username links. * @param string $v CSS class for username links.
* *
* @return Autolink Fluid method chaining. * @return Autolink Fluid method chaining.
*/ */
public function setUsernameClass($v) public function setUsernameClass($v)
{ {
$this->class_user = trim($v); $this->class_user = trim($v);
return $this; return $this;
} }
/** /**
* CSS class for auto-linked username/list URLs. * CSS class for auto-linked username/list URLs.
* *
* @return string CSS class for username/list links. * @return string CSS class for username/list links.
*/ */
public function getListClass() public function getListClass()
{ {
@ -250,20 +244,21 @@ class Autolink extends Regex
/** /**
* CSS class for auto-linked username/list URLs. * CSS class for auto-linked username/list URLs.
* *
* @param string $v CSS class for username/list links. * @param string $v CSS class for username/list links.
* *
* @return Autolink Fluid method chaining. * @return Autolink Fluid method chaining.
*/ */
public function setListClass($v) public function setListClass($v)
{ {
$this->class_list = trim($v); $this->class_list = trim($v);
return $this; return $this;
} }
/** /**
* CSS class for auto-linked hashtag URLs. * CSS class for auto-linked hashtag URLs.
* *
* @return string CSS class for hashtag links. * @return string CSS class for hashtag links.
*/ */
public function getHashtagClass() public function getHashtagClass()
{ {
@ -273,20 +268,21 @@ class Autolink extends Regex
/** /**
* CSS class for auto-linked hashtag URLs. * CSS class for auto-linked hashtag URLs.
* *
* @param string $v CSS class for hashtag links. * @param string $v CSS class for hashtag links.
* *
* @return Autolink Fluid method chaining. * @return Autolink Fluid method chaining.
*/ */
public function setHashtagClass($v) public function setHashtagClass($v)
{ {
$this->class_hash = trim($v); $this->class_hash = trim($v);
return $this; return $this;
} }
/** /**
* CSS class for auto-linked cashtag URLs. * CSS class for auto-linked cashtag URLs.
* *
* @return string CSS class for cashtag links. * @return string CSS class for cashtag links.
*/ */
public function getCashtagClass() public function getCashtagClass()
{ {
@ -296,20 +292,21 @@ class Autolink extends Regex
/** /**
* CSS class for auto-linked cashtag URLs. * CSS class for auto-linked cashtag URLs.
* *
* @param string $v CSS class for cashtag links. * @param string $v CSS class for cashtag links.
* *
* @return Autolink Fluid method chaining. * @return Autolink Fluid method chaining.
*/ */
public function setCashtagClass($v) public function setCashtagClass($v)
{ {
$this->class_cash = trim($v); $this->class_cash = trim($v);
return $this; return $this;
} }
/** /**
* Whether to include the value 'nofollow' in the 'rel' attribute. * Whether to include the value 'nofollow' in the 'rel' attribute.
* *
* @return bool Whether to add 'nofollow' to the 'rel' attribute. * @return bool Whether to add 'nofollow' to the 'rel' attribute.
*/ */
public function getNoFollow() public function getNoFollow()
{ {
@ -319,13 +316,14 @@ class Autolink extends Regex
/** /**
* Whether to include the value 'nofollow' in the 'rel' attribute. * Whether to include the value 'nofollow' in the 'rel' attribute.
* *
* @param bool $v The value to add to the 'target' attribute. * @param bool $v The value to add to the 'target' attribute.
* *
* @return Autolink Fluid method chaining. * @return Autolink Fluid method chaining.
*/ */
public function setNoFollow($v) public function setNoFollow($v)
{ {
$this->nofollow = $v; $this->nofollow = $v;
return $this; return $this;
} }
@ -337,7 +335,7 @@ class Autolink extends Regex
* been undeprecated and thus the 'target' attribute can be used. If this is * been undeprecated and thus the 'target' attribute can be used. If this is
* set to false then the 'target' attribute will be output. * set to false then the 'target' attribute will be output.
* *
* @return bool Whether to add 'external' to the 'rel' attribute. * @return bool Whether to add 'external' to the 'rel' attribute.
*/ */
public function getExternal() public function getExternal()
{ {
@ -352,13 +350,14 @@ class Autolink extends Regex
* been undeprecated and thus the 'target' attribute can be used. If this is * been undeprecated and thus the 'target' attribute can be used. If this is
* set to false then the 'target' attribute will be output. * set to false then the 'target' attribute will be output.
* *
* @param bool $v The value to add to the 'target' attribute. * @param bool $v The value to add to the 'target' attribute.
* *
* @return Autolink Fluid method chaining. * @return Autolink Fluid method chaining.
*/ */
public function setExternal($v) public function setExternal($v)
{ {
$this->external = $v; $this->external = $v;
return $this; return $this;
} }
@ -369,7 +368,7 @@ class Autolink extends Regex
* since been reinstated in HTML 5. To output the 'target' attribute you * since been reinstated in HTML 5. To output the 'target' attribute you
* must disable the adding of the string 'external' to the 'rel' attribute. * must disable the adding of the string 'external' to the 'rel' attribute.
* *
* @return string The value to add to the 'target' attribute. * @return string The value to add to the 'target' attribute.
*/ */
public function getTarget() public function getTarget()
{ {
@ -383,22 +382,25 @@ class Autolink extends Regex
* since been reinstated in HTML 5. To output the 'target' attribute you * since been reinstated in HTML 5. To output the 'target' attribute you
* must disable the adding of the string 'external' to the 'rel' attribute. * must disable the adding of the string 'external' to the 'rel' attribute.
* *
* @param string $v The value to add to the 'target' attribute. * @param string $v The value to add to the 'target' attribute.
* *
* @return Autolink Fluid method chaining. * @return Autolink Fluid method chaining.
*/ */
public function setTarget($v) public function setTarget($v)
{ {
$this->target = trim($v); $this->target = trim($v);
return $this; return $this;
} }
/** /**
* Autolink with entities * Autolink with entities.
* *
* @param string $tweet * @param string $tweet
* @param array $entities * @param array $entities
*
* @return string * @return string
*
* @since 1.1.0 * @since 1.1.0
*/ */
public function autoLinkEntities($tweet = null, $entities = null) public function autoLinkEntities($tweet = null, $entities = null)
@ -428,6 +430,7 @@ class Autolink extends Regex
$beginIndex = $entity['indices'][1]; $beginIndex = $entity['indices'][1];
} }
$text .= StringUtils::substr($tweet, $beginIndex, StringUtils::strlen($tweet)); $text .= StringUtils::substr($tweet, $beginIndex, StringUtils::strlen($tweet));
return $text; return $text;
} }
@ -436,13 +439,15 @@ class Autolink extends Regex
* *
* @param string The tweet to be converted * @param string The tweet to be converted
* @param mixed The entities info * @param mixed The entities info
*
* @return string that auto-link HTML added * @return string that auto-link HTML added
*
* @since 1.1.0 * @since 1.1.0
*/ */
public function autoLinkWithJson($tweet = null, $json = null) public function autoLinkWithJson($tweet = null, $json = null)
{ {
// concatenate entities // concatenate entities
$entities = array(); $entities = [];
if (is_object($json)) { if (is_object($json)) {
$json = $this->object2array($json); $json = $this->object2array($json);
} }
@ -460,13 +465,15 @@ class Autolink extends Regex
} }
$entities = $this->extractor->removeOverlappingEntities($entities); $entities = $this->extractor->removeOverlappingEntities($entities);
return $this->autoLinkEntities($tweet, $entities); return $this->autoLinkEntities($tweet, $entities);
} }
/** /**
* convert Object to Array * convert Object to Array.
* *
* @param mixed $obj * @param mixed $obj
*
* @return array * @return array
*/ */
protected function object2array($obj) protected function object2array($obj)
@ -477,6 +484,7 @@ class Autolink extends Regex
$array[$key] = $this->object2array($var); $array[$key] = $this->object2array($var);
} }
} }
return $array; return $array;
} }
@ -484,7 +492,9 @@ class Autolink extends Regex
* Auto-link hashtags, URLs, usernames and lists. * Auto-link hashtags, URLs, usernames and lists.
* *
* @param string The tweet to be converted * @param string The tweet to be converted
*
* @return string that auto-link HTML added * @return string that auto-link HTML added
*
* @since 1.1.0 * @since 1.1.0
*/ */
public function autoLink($tweet = null) public function autoLink($tweet = null)
@ -493,6 +503,7 @@ class Autolink extends Regex
$tweet = $this->tweet; $tweet = $this->tweet;
} }
$entities = $this->extractor->extractURLWithoutProtocol(false)->extractEntitiesWithIndices($tweet); $entities = $this->extractor->extractURLWithoutProtocol(false)->extractEntitiesWithIndices($tweet);
return $this->autoLinkEntities($tweet, $entities); return $this->autoLinkEntities($tweet, $entities);
} }
@ -502,6 +513,7 @@ class Autolink extends Regex
* added. * added.
* *
* @return string that auto-link HTML added * @return string that auto-link HTML added
*
* @since 1.1.0 * @since 1.1.0
*/ */
public function autoLinkUsernamesAndLists($tweet = null) public function autoLinkUsernamesAndLists($tweet = null)
@ -510,6 +522,7 @@ class Autolink extends Regex
$tweet = $this->tweet; $tweet = $this->tweet;
} }
$entities = $this->extractor->extractMentionsOrListsWithIndices($tweet); $entities = $this->extractor->extractMentionsOrListsWithIndices($tweet);
return $this->autoLinkEntities($tweet, $entities); return $this->autoLinkEntities($tweet, $entities);
} }
@ -518,6 +531,7 @@ class Autolink extends Regex
* added. * added.
* *
* @return string that auto-link HTML added * @return string that auto-link HTML added
*
* @since 1.1.0 * @since 1.1.0
*/ */
public function autoLinkHashtags($tweet = null) public function autoLinkHashtags($tweet = null)
@ -526,6 +540,7 @@ class Autolink extends Regex
$tweet = $this->tweet; $tweet = $this->tweet;
} }
$entities = $this->extractor->extractHashtagsWithIndices($tweet); $entities = $this->extractor->extractHashtagsWithIndices($tweet);
return $this->autoLinkEntities($tweet, $entities); return $this->autoLinkEntities($tweet, $entities);
} }
@ -535,6 +550,7 @@ class Autolink extends Regex
* This only auto-links URLs with protocol. * This only auto-links URLs with protocol.
* *
* @return string that auto-link HTML added * @return string that auto-link HTML added
*
* @since 1.1.0 * @since 1.1.0
*/ */
public function autoLinkURLs($tweet = null) public function autoLinkURLs($tweet = null)
@ -543,6 +559,7 @@ class Autolink extends Regex
$tweet = $this->tweet; $tweet = $this->tweet;
} }
$entities = $this->extractor->extractURLWithoutProtocol(false)->extractURLsWithIndices($tweet); $entities = $this->extractor->extractURLWithoutProtocol(false)->extractURLsWithIndices($tweet);
return $this->autoLinkEntities($tweet, $entities); return $this->autoLinkEntities($tweet, $entities);
} }
@ -551,6 +568,7 @@ class Autolink extends Regex
* added. * added.
* *
* @return string that auto-link HTML added * @return string that auto-link HTML added
*
* @since 1.1.0 * @since 1.1.0
*/ */
public function autoLinkCashtags($tweet = null) public function autoLinkCashtags($tweet = null)
@ -559,6 +577,7 @@ class Autolink extends Regex
$tweet = $this->tweet; $tweet = $this->tweet;
} }
$entities = $this->extractor->extractCashtagsWithIndices($tweet); $entities = $this->extractor->extractCashtagsWithIndices($tweet);
return $this->autoLinkEntities($tweet, $entities); return $this->autoLinkEntities($tweet, $entities);
} }
@ -639,10 +658,11 @@ class Autolink extends Regex
} }
/** /**
*
* @param array $entity * @param array $entity
* @param string $tweet * @param string $tweet
*
* @return string * @return string
*
* @since 1.1.0 * @since 1.1.0
*/ */
public function linkToHashtag($entity, $tweet = null) public function linkToHashtag($entity, $tweet = null)
@ -651,13 +671,13 @@ class Autolink extends Regex
$tweet = $this->tweet; $tweet = $this->tweet;
} }
$this->target = false; $this->target = false;
$attributes = array(); $attributes = [];
$class = array(); $class = [];
$hash = StringUtils::substr($tweet, $entity['indices'][0], 1); $hash = StringUtils::substr($tweet, $entity['indices'][0], 1);
$linkText = $hash . $entity['hashtag']; $linkText = $hash.$entity['hashtag'];
$attributes['href'] = $this->url_base_hash . $entity['hashtag'] . '?src=hash'; $attributes['href'] = $this->url_base_hash.$entity['hashtag'].'?src=hash';
$attributes['title'] = '#' . $entity['hashtag']; $attributes['title'] = '#'.$entity['hashtag'];
if (!empty($this->class_hash)) { if (!empty($this->class_hash)) {
$class[] = $this->class_hash; $class[] = $this->class_hash;
} }
@ -665,32 +685,33 @@ class Autolink extends Regex
$class[] = 'rtl'; $class[] = 'rtl';
} }
if (!empty($class)) { if (!empty($class)) {
$attributes['class'] = join(' ', $class); $attributes['class'] = implode(' ', $class);
} }
return $this->linkToText($entity, $linkText, $attributes); return $this->linkToText($entity, $linkText, $attributes);
} }
/** /**
* @param array $entity
* *
* @param array $entity
* @return string * @return string
*
* @since 1.1.0 * @since 1.1.0
*/ */
public function linkToMentionAndList($entity) public function linkToMentionAndList($entity)
{ {
$attributes = array(); $attributes = [];
if (!empty($entity['list_slug'])) { if (!empty($entity['list_slug'])) {
# Replace the list and username // Replace the list and username
$linkText = $entity['screen_name'] . $entity['list_slug']; $linkText = $entity['screen_name'].$entity['list_slug'];
$class = $this->class_list; $class = $this->class_list;
$url = $this->url_base_list . $linkText; $url = $this->url_base_list.$linkText;
} else { } else {
# Replace the username // Replace the username
$linkText = $entity['screen_name']; $linkText = $entity['screen_name'];
$class = $this->class_user; $class = $this->class_user;
$url = $this->url_base_user . $linkText; $url = $this->url_base_user.$linkText;
} }
if (!empty($class)) { if (!empty($class)) {
$attributes['class'] = $class; $attributes['class'] = $class;
@ -701,10 +722,11 @@ class Autolink extends Regex
} }
/** /**
*
* @param array $entity * @param array $entity
* @param string $tweet * @param string $tweet
*
* @return string * @return string
*
* @since 1.1.0 * @since 1.1.0
*/ */
public function linkToCashtag($entity, $tweet = null) public function linkToCashtag($entity, $tweet = null)
@ -712,10 +734,10 @@ class Autolink extends Regex
if (is_null($tweet)) { if (is_null($tweet)) {
$tweet = $this->tweet; $tweet = $this->tweet;
} }
$attributes = array(); $attributes = [];
$doller = StringUtils::substr($tweet, $entity['indices'][0], 1); $doller = StringUtils::substr($tweet, $entity['indices'][0], 1);
$linkText = $doller . $entity['cashtag']; $linkText = $doller.$entity['cashtag'];
$attributes['href'] = $this->url_base_cash . $entity['cashtag']; $attributes['href'] = $this->url_base_cash.$entity['cashtag'];
$attributes['title'] = $linkText; $attributes['title'] = $linkText;
if (!empty($this->class_cash)) { if (!empty($this->class_cash)) {
$attributes['class'] = $this->class_cash; $attributes['class'] = $this->class_cash;
@ -725,16 +747,17 @@ class Autolink extends Regex
} }
/** /**
* * @param array $entity
* @param array $entity
* @param string $text * @param string $text
* @param array $attributes * @param array $attributes
*
* @return string * @return string
*
* @since 1.1.0 * @since 1.1.0
*/ */
public function linkToText(array $entity, $text, $attributes = array()) public function linkToText(array $entity, $text, $attributes = [])
{ {
$rel = array(); $rel = [];
if ($this->external) { if ($this->external) {
$rel[] = 'external'; $rel[] = 'external';
} }
@ -745,23 +768,25 @@ class Autolink extends Regex
$rel[] = 'noopener'; $rel[] = 'noopener';
} }
if (!empty($rel)) { if (!empty($rel)) {
$attributes['rel'] = join(' ', $rel); $attributes['rel'] = implode(' ', $rel);
} }
if ($this->target) { if ($this->target) {
$attributes['target'] = $this->target; $attributes['target'] = $this->target;
} }
$link = '<a'; $link = '<a';
foreach ($attributes as $key => $val) { foreach ($attributes as $key => $val) {
$link .= ' ' . $key . '="' . $this->escapeHTML($val) . '"'; $link .= ' '.$key.'="'.$this->escapeHTML($val).'"';
} }
$link .= '>' . $text . '</a>'; $link .= '>'.$text.'</a>';
return $link; return $link;
} }
/** /**
* html escape * html escape.
* *
* @param string $text * @param string $text
*
* @return string * @return string
*/ */
protected function escapeHTML($text) protected function escapeHTML($text)

View file

@ -5,16 +5,12 @@
* @author Nick Pope <nick@nickpope.me.uk> * @author Nick Pope <nick@nickpope.me.uk>
* @copyright Copyright © 2010, Mike Cochrane, Nick Pope * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter.Text
*/ */
namespace App\Util\Lexer; namespace App\Util\Lexer;
use App\Util\Lexer\Regex;
use App\Util\Lexer\StringUtils;
/** /**
* Twitter Extractor Class * Twitter Extractor Class.
* *
* Parses tweets and extracts URLs, usernames, username/list pairs and * Parses tweets and extracts URLs, usernames, username/list pairs and
* hashtags. * hashtags.
@ -27,24 +23,22 @@ use App\Util\Lexer\StringUtils;
* @author Nick Pope <nick@nickpope.me.uk> * @author Nick Pope <nick@nickpope.me.uk>
* @copyright Copyright © 2010, Mike Cochrane, Nick Pope * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter.Text
*/ */
class Extractor extends Regex class Extractor extends Regex
{ {
/** /**
* @var boolean * @var bool
*/ */
protected $extractURLWithoutProtocol = true; protected $extractURLWithoutProtocol = true;
/** /**
* Provides fluent method chaining. * Provides fluent method chaining.
* *
* @param string $tweet The tweet to be converted. * @param string $tweet The tweet to be converted.
* *
* @see __construct() * @see __construct()
* *
* @return Extractor * @return Extractor
*/ */
public static function create($tweet = null) public static function create($tweet = null)
{ {
@ -56,7 +50,7 @@ class Extractor extends Regex
* *
* Extracts various parts of a tweet including URLs, usernames, hashtags... * Extracts various parts of a tweet including URLs, usernames, hashtags...
* *
* @param string $tweet The tweet to extract. * @param string $tweet The tweet to extract.
*/ */
public function __construct($tweet = null) public function __construct($tweet = null)
{ {
@ -67,29 +61,32 @@ class Extractor extends Regex
* Extracts all parts of a tweet and returns an associative array containing * Extracts all parts of a tweet and returns an associative array containing
* the extracted elements. * the extracted elements.
* *
* @param string $tweet The tweet to extract. * @param string $tweet The tweet to extract.
* @return array The elements in the tweet. *
* @return array The elements in the tweet.
*/ */
public function extract($tweet = null) public function extract($tweet = null)
{ {
if (is_null($tweet)) { if (is_null($tweet)) {
$tweet = $this->tweet; $tweet = $this->tweet;
} }
return array(
'hashtags' => $this->extractHashtags($tweet), return [
'urls' => $this->extractURLs($tweet), 'hashtags' => $this->extractHashtags($tweet),
'mentions' => $this->extractMentionedUsernames($tweet), 'urls' => $this->extractURLs($tweet),
'replyto' => $this->extractRepliedUsernames($tweet), 'mentions' => $this->extractMentionedUsernames($tweet),
'replyto' => $this->extractRepliedUsernames($tweet),
'hashtags_with_indices' => $this->extractHashtagsWithIndices($tweet), 'hashtags_with_indices' => $this->extractHashtagsWithIndices($tweet),
'urls_with_indices' => $this->extractURLsWithIndices($tweet), 'urls_with_indices' => $this->extractURLsWithIndices($tweet),
'mentions_with_indices' => $this->extractMentionedUsernamesWithIndices($tweet), 'mentions_with_indices' => $this->extractMentionedUsernamesWithIndices($tweet),
); ];
} }
/** /**
* Extract URLs, @mentions, lists and #hashtag from a given text/tweet. * Extract URLs, @mentions, lists and #hashtag from a given text/tweet.
* *
* @param string $tweet The tweet to extract. * @param string $tweet The tweet to extract.
*
* @return array list of extracted entities * @return array list of extracted entities
*/ */
public function extractEntitiesWithIndices($tweet = null) public function extractEntitiesWithIndices($tweet = null)
@ -97,63 +94,70 @@ class Extractor extends Regex
if (is_null($tweet)) { if (is_null($tweet)) {
$tweet = $this->tweet; $tweet = $this->tweet;
} }
$entities = array(); $entities = [];
$entities = array_merge($entities, $this->extractURLsWithIndices($tweet)); $entities = array_merge($entities, $this->extractURLsWithIndices($tweet));
$entities = array_merge($entities, $this->extractHashtagsWithIndices($tweet, false)); $entities = array_merge($entities, $this->extractHashtagsWithIndices($tweet, false));
$entities = array_merge($entities, $this->extractMentionsOrListsWithIndices($tweet)); $entities = array_merge($entities, $this->extractMentionsOrListsWithIndices($tweet));
$entities = array_merge($entities, $this->extractCashtagsWithIndices($tweet)); $entities = array_merge($entities, $this->extractCashtagsWithIndices($tweet));
$entities = $this->removeOverlappingEntities($entities); $entities = $this->removeOverlappingEntities($entities);
return $entities; return $entities;
} }
/** /**
* Extracts all the hashtags from the tweet. * Extracts all the hashtags from the tweet.
* *
* @param string $tweet The tweet to extract. * @param string $tweet The tweet to extract.
* @return array The hashtag elements in the tweet. *
* @return array The hashtag elements in the tweet.
*/ */
public function extractHashtags($tweet = null) public function extractHashtags($tweet = null)
{ {
$hashtagsOnly = array(); $hashtagsOnly = [];
$hashtagsWithIndices = $this->extractHashtagsWithIndices($tweet); $hashtagsWithIndices = $this->extractHashtagsWithIndices($tweet);
foreach ($hashtagsWithIndices as $hashtagWithIndex) { foreach ($hashtagsWithIndices as $hashtagWithIndex) {
$hashtagsOnly[] = $hashtagWithIndex['hashtag']; $hashtagsOnly[] = $hashtagWithIndex['hashtag'];
} }
return $hashtagsOnly; return $hashtagsOnly;
} }
/** /**
* Extracts all the cashtags from the tweet. * Extracts all the cashtags from the tweet.
* *
* @param string $tweet The tweet to extract. * @param string $tweet The tweet to extract.
* @return array The cashtag elements in the tweet. *
* @return array The cashtag elements in the tweet.
*/ */
public function extractCashtags($tweet = null) public function extractCashtags($tweet = null)
{ {
$cashtagsOnly = array(); $cashtagsOnly = [];
$cashtagsWithIndices = $this->extractCashtagsWithIndices($tweet); $cashtagsWithIndices = $this->extractCashtagsWithIndices($tweet);
foreach ($cashtagsWithIndices as $cashtagWithIndex) { foreach ($cashtagsWithIndices as $cashtagWithIndex) {
$cashtagsOnly[] = $cashtagWithIndex['cashtag']; $cashtagsOnly[] = $cashtagWithIndex['cashtag'];
} }
return $cashtagsOnly; return $cashtagsOnly;
} }
/** /**
* Extracts all the URLs from the tweet. * Extracts all the URLs from the tweet.
* *
* @param string $tweet The tweet to extract. * @param string $tweet The tweet to extract.
* @return array The URL elements in the tweet. *
* @return array The URL elements in the tweet.
*/ */
public function extractURLs($tweet = null) public function extractURLs($tweet = null)
{ {
$urlsOnly = array(); $urlsOnly = [];
$urlsWithIndices = $this->extractURLsWithIndices($tweet); $urlsWithIndices = $this->extractURLsWithIndices($tweet);
foreach ($urlsWithIndices as $urlWithIndex) { foreach ($urlsWithIndices as $urlWithIndex) {
$urlsOnly[] = $urlWithIndex['url']; $urlsOnly[] = $urlWithIndex['url'];
} }
return $urlsOnly; return $urlsOnly;
} }
@ -162,21 +166,23 @@ class Extractor extends Regex
* *
* A mention is an occurrence of a username anywhere in a tweet. * A mention is an occurrence of a username anywhere in a tweet.
* *
* @param string $tweet The tweet to extract. * @param string $tweet The tweet to extract.
* @return array The usernames elements in the tweet. *
* @return array The usernames elements in the tweet.
*/ */
public function extractMentionedScreennames($tweet = null) public function extractMentionedScreennames($tweet = null)
{ {
$usernamesOnly = array(); $usernamesOnly = [];
$mentionsWithIndices = $this->extractMentionsOrListsWithIndices($tweet); $mentionsWithIndices = $this->extractMentionsOrListsWithIndices($tweet);
foreach ($mentionsWithIndices as $mentionWithIndex) { foreach ($mentionsWithIndices as $mentionWithIndex) {
$screen_name = mb_strtolower($mentionWithIndex['screen_name']); $screen_name = mb_strtolower($mentionWithIndex['screen_name']);
if (empty($screen_name) OR in_array($screen_name, $usernamesOnly)) { if (empty($screen_name) or in_array($screen_name, $usernamesOnly)) {
continue; continue;
} }
$usernamesOnly[] = $screen_name; $usernamesOnly[] = $screen_name;
} }
return $usernamesOnly; return $usernamesOnly;
} }
@ -185,12 +191,14 @@ class Extractor extends Regex
* *
* A mention is an occurrence of a username anywhere in a tweet. * A mention is an occurrence of a username anywhere in a tweet.
* *
* @return array The usernames elements in the tweet. * @return array The usernames elements in the tweet.
*
* @deprecated since version 1.1.0 * @deprecated since version 1.1.0
*/ */
public function extractMentionedUsernames($tweet) public function extractMentionedUsernames($tweet)
{ {
$this->tweet = $tweet; $this->tweet = $tweet;
return $this->extractMentionedScreennames($tweet); return $this->extractMentionedScreennames($tweet);
} }
@ -199,8 +207,9 @@ class Extractor extends Regex
* *
* A reply is an occurrence of a username at the beginning of a tweet. * A reply is an occurrence of a username at the beginning of a tweet.
* *
* @param string $tweet The tweet to extract. * @param string $tweet The tweet to extract.
* @return array The usernames replied to in a tweet. *
* @return array The usernames replied to in a tweet.
*/ */
public function extractReplyScreenname($tweet = null) public function extractReplyScreenname($tweet = null)
{ {
@ -208,10 +217,11 @@ class Extractor extends Regex
$tweet = $this->tweet; $tweet = $this->tweet;
} }
$matched = preg_match(self::$patterns['valid_reply'], $tweet, $matches); $matched = preg_match(self::$patterns['valid_reply'], $tweet, $matches);
# Check username ending in // Check username ending in
if ($matched && preg_match(self::$patterns['end_mention_match'], $matches[2])) { if ($matched && preg_match(self::$patterns['end_mention_match'], $matches[2])) {
$matched = false; $matched = false;
} }
return $matched ? $matches[1] : null; return $matched ? $matches[1] : null;
} }
@ -220,7 +230,8 @@ class Extractor extends Regex
* *
* A reply is an occurrence of a username at the beginning of a tweet. * A reply is an occurrence of a username at the beginning of a tweet.
* *
* @return array The usernames replied to in a tweet. * @return array The usernames replied to in a tweet.
*
* @deprecated since version 1.1.0 * @deprecated since version 1.1.0
*/ */
public function extractRepliedUsernames() public function extractRepliedUsernames()
@ -231,9 +242,10 @@ class Extractor extends Regex
/** /**
* Extracts all the hashtags and the indices they occur at from the tweet. * Extracts all the hashtags and the indices they occur at from the tweet.
* *
* @param string $tweet The tweet to extract. * @param string $tweet The tweet to extract.
* @param boolean $checkUrlOverlap if true, check if extracted hashtags overlap URLs and remove overlapping ones * @param bool $checkUrlOverlap if true, check if extracted hashtags overlap URLs and remove overlapping ones
* @return array The hashtag elements in the tweet. *
* @return array The hashtag elements in the tweet.
*/ */
public function extractHashtagsWithIndices($tweet = null, $checkUrlOverlap = true) public function extractHashtagsWithIndices($tweet = null, $checkUrlOverlap = true)
{ {
@ -242,36 +254,36 @@ class Extractor extends Regex
} }
if (!preg_match('/[#]/iu', $tweet)) { if (!preg_match('/[#]/iu', $tweet)) {
return array(); return [];
} }
preg_match_all(self::$patterns['valid_hashtag'], $tweet, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE); preg_match_all(self::$patterns['valid_hashtag'], $tweet, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
$tags = array(); $tags = [];
foreach ($matches as $match) { foreach ($matches as $match) {
list($all, $before, $hash, $hashtag, $outer) = array_pad($match, 3, array('', 0)); list($all, $before, $hash, $hashtag, $outer) = array_pad($match, 3, ['', 0]);
$start_position = $hash[1] > 0 ? StringUtils::strlen(substr($tweet, 0, $hash[1])) : $hash[1]; $start_position = $hash[1] > 0 ? StringUtils::strlen(substr($tweet, 0, $hash[1])) : $hash[1];
$end_position = $start_position + StringUtils::strlen($hash[0] . $hashtag[0]); $end_position = $start_position + StringUtils::strlen($hash[0].$hashtag[0]);
if (preg_match(self::$patterns['end_hashtag_match'], $outer[0])) { if (preg_match(self::$patterns['end_hashtag_match'], $outer[0])) {
continue; continue;
} }
$tags[] = array( $tags[] = [
'hashtag' => $hashtag[0], 'hashtag' => $hashtag[0],
'indices' => array($start_position, $end_position) 'indices' => [$start_position, $end_position],
); ];
} }
if (!$checkUrlOverlap) { if (!$checkUrlOverlap) {
return $tags; return $tags;
} }
# check url overlap // check url overlap
$urls = $this->extractURLsWithIndices($tweet); $urls = $this->extractURLsWithIndices($tweet);
$entities = $this->removeOverlappingEntities(array_merge($tags, $urls)); $entities = $this->removeOverlappingEntities(array_merge($tags, $urls));
$validTags = array(); $validTags = [];
foreach ($entities as $entity) { foreach ($entities as $entity) {
if (empty($entity['hashtag'])) { if (empty($entity['hashtag'])) {
continue; continue;
@ -285,8 +297,9 @@ class Extractor extends Regex
/** /**
* Extracts all the cashtags and the indices they occur at from the tweet. * Extracts all the cashtags and the indices they occur at from the tweet.
* *
* @param string $tweet The tweet to extract. * @param string $tweet The tweet to extract.
* @return array The cashtag elements in the tweet. *
* @return array The cashtag elements in the tweet.
*/ */
public function extractCashtagsWithIndices($tweet = null) public function extractCashtagsWithIndices($tweet = null)
{ {
@ -295,25 +308,25 @@ class Extractor extends Regex
} }
if (!preg_match('/\$/iu', $tweet)) { if (!preg_match('/\$/iu', $tweet)) {
return array(); return [];
} }
preg_match_all(self::$patterns['valid_cashtag'], $tweet, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE); preg_match_all(self::$patterns['valid_cashtag'], $tweet, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
$tags = array(); $tags = [];
foreach ($matches as $match) { foreach ($matches as $match) {
list($all, $before, $dollar, $cash_text, $outer) = array_pad($match, 3, array('', 0)); list($all, $before, $dollar, $cash_text, $outer) = array_pad($match, 3, ['', 0]);
$start_position = $dollar[1] > 0 ? StringUtils::strlen(substr($tweet, 0, $dollar[1])) : $dollar[1]; $start_position = $dollar[1] > 0 ? StringUtils::strlen(substr($tweet, 0, $dollar[1])) : $dollar[1];
$end_position = $start_position + StringUtils::strlen($dollar[0] . $cash_text[0]); $end_position = $start_position + StringUtils::strlen($dollar[0].$cash_text[0]);
if (preg_match(self::$patterns['end_hashtag_match'], $outer[0])) { if (preg_match(self::$patterns['end_hashtag_match'], $outer[0])) {
continue; continue;
} }
$tags[] = array( $tags[] = [
'cashtag' => $cash_text[0], 'cashtag' => $cash_text[0],
'indices' => array($start_position, $end_position) 'indices' => [$start_position, $end_position],
); ];
} }
return $tags; return $tags;
@ -322,8 +335,9 @@ class Extractor extends Regex
/** /**
* Extracts all the URLs and the indices they occur at from the tweet. * Extracts all the URLs and the indices they occur at from the tweet.
* *
* @param string $tweet The tweet to extract. * @param string $tweet The tweet to extract.
* @return array The URLs elements in the tweet. *
* @return array The URLs elements in the tweet.
*/ */
public function extractURLsWithIndices($tweet = null) public function extractURLsWithIndices($tweet = null)
{ {
@ -333,14 +347,14 @@ class Extractor extends Regex
$needle = $this->extractURLWithoutProtocol() ? '.' : ':'; $needle = $this->extractURLWithoutProtocol() ? '.' : ':';
if (strpos($tweet, $needle) === false) { if (strpos($tweet, $needle) === false) {
return array(); return [];
} }
$urls = array(); $urls = [];
preg_match_all(self::$patterns['valid_url'], $tweet, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE); preg_match_all(self::$patterns['valid_url'], $tweet, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
foreach ($matches as $match) { foreach ($matches as $match) {
list($all, $before, $url, $protocol, $domain, $port, $path, $query) = array_pad($match, 8, array('')); list($all, $before, $url, $protocol, $domain, $port, $path, $query) = array_pad($match, 8, ['']);
$start_position = $url[1] > 0 ? StringUtils::strlen(substr($tweet, 0, $url[1])) : $url[1]; $start_position = $url[1] > 0 ? StringUtils::strlen(substr($tweet, 0, $url[1])) : $url[1];
$end_position = $start_position + StringUtils::strlen($url[0]); $end_position = $start_position + StringUtils::strlen($url[0]);
@ -364,13 +378,13 @@ class Extractor extends Regex
$ascii_end_position = 0; $ascii_end_position = 0;
if (preg_match(self::$patterns['valid_ascii_domain'], $domain, $asciiDomain)) { if (preg_match(self::$patterns['valid_ascii_domain'], $domain, $asciiDomain)) {
$asciiDomain[0] = preg_replace('/' . preg_quote($domain, '/') . '/u', $asciiDomain[0], $url); $asciiDomain[0] = preg_replace('/'.preg_quote($domain, '/').'/u', $asciiDomain[0], $url);
$ascii_start_position = StringUtils::strpos($domain, $asciiDomain[0], $ascii_end_position); $ascii_start_position = StringUtils::strpos($domain, $asciiDomain[0], $ascii_end_position);
$ascii_end_position = $ascii_start_position + StringUtils::strlen($asciiDomain[0]); $ascii_end_position = $ascii_start_position + StringUtils::strlen($asciiDomain[0]);
$last_url = array( $last_url = [
'url' => $asciiDomain[0], 'url' => $asciiDomain[0],
'indices' => array($start_position + $ascii_start_position, $start_position + $ascii_end_position), 'indices' => [$start_position + $ascii_start_position, $start_position + $ascii_end_position],
); ];
if (!empty($path) if (!empty($path)
|| preg_match(self::$patterns['valid_special_short_domain'], $asciiDomain[0]) || preg_match(self::$patterns['valid_special_short_domain'], $asciiDomain[0])
|| !preg_match(self::$patterns['invalid_short_domain'], $asciiDomain[0])) { || !preg_match(self::$patterns['invalid_short_domain'], $asciiDomain[0])) {
@ -386,7 +400,7 @@ class Extractor extends Regex
// $last_url only contains domain. Need to add path and query if they exist. // $last_url only contains domain. Need to add path and query if they exist.
if (!empty($path)) { if (!empty($path)) {
// last_url was not added. Add it to urls here. // last_url was not added. Add it to urls here.
$last_url['url'] = preg_replace('/' . preg_quote($domain, '/') . '/u', $last_url['url'], $url); $last_url['url'] = preg_replace('/'.preg_quote($domain, '/').'/u', $last_url['url'], $url);
$last_url['indices'][1] = $end_position; $last_url['indices'][1] = $end_position;
} }
} else { } else {
@ -395,10 +409,10 @@ class Extractor extends Regex
$url = $tcoUrlMatches[0]; $url = $tcoUrlMatches[0];
$end_position = $start_position + StringUtils::strlen($url); $end_position = $start_position + StringUtils::strlen($url);
} }
$urls[] = array( $urls[] = [
'url' => $url, 'url' => $url,
'indices' => array($start_position, $end_position), 'indices' => [$start_position, $end_position],
); ];
} }
} }
@ -408,8 +422,9 @@ class Extractor extends Regex
/** /**
* Extracts all the usernames and the indices they occur at from the tweet. * Extracts all the usernames and the indices they occur at from the tweet.
* *
* @param string $tweet The tweet to extract. * @param string $tweet The tweet to extract.
* @return array The username elements in the tweet. *
* @return array The username elements in the tweet.
*/ */
public function extractMentionedScreennamesWithIndices($tweet = null) public function extractMentionedScreennamesWithIndices($tweet = null)
{ {
@ -417,7 +432,7 @@ class Extractor extends Regex
$tweet = $this->tweet; $tweet = $this->tweet;
} }
$usernamesOnly = array(); $usernamesOnly = [];
$mentions = $this->extractMentionsOrListsWithIndices($tweet); $mentions = $this->extractMentionsOrListsWithIndices($tweet);
foreach ($mentions as $mention) { foreach ($mentions as $mention) {
if (isset($mention['list_slug'])) { if (isset($mention['list_slug'])) {
@ -425,13 +440,15 @@ class Extractor extends Regex
} }
$usernamesOnly[] = $mention; $usernamesOnly[] = $mention;
} }
return $usernamesOnly; return $usernamesOnly;
} }
/** /**
* Extracts all the usernames and the indices they occur at from the tweet. * Extracts all the usernames and the indices they occur at from the tweet.
* *
* @return array The username elements in the tweet. * @return array The username elements in the tweet.
*
* @deprecated since version 1.1.0 * @deprecated since version 1.1.0
*/ */
public function extractMentionedUsernamesWithIndices() public function extractMentionedUsernamesWithIndices()
@ -442,8 +459,9 @@ class Extractor extends Regex
/** /**
* Extracts all the usernames and the indices they occur at from the tweet. * Extracts all the usernames and the indices they occur at from the tweet.
* *
* @param string $tweet The tweet to extract. * @param string $tweet The tweet to extract.
* @return array The username elements in the tweet. *
* @return array The username elements in the tweet.
*/ */
public function extractMentionsOrListsWithIndices($tweet = null) public function extractMentionsOrListsWithIndices($tweet = null)
{ {
@ -452,21 +470,21 @@ class Extractor extends Regex
} }
if (!preg_match('/[@]/iu', $tweet)) { if (!preg_match('/[@]/iu', $tweet)) {
return array(); return [];
} }
preg_match_all(self::$patterns['valid_mentions_or_lists'], $tweet, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE); preg_match_all(self::$patterns['valid_mentions_or_lists'], $tweet, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
$results = array(); $results = [];
foreach ($matches as $match) { foreach ($matches as $match) {
list($all, $before, $at, $username, $list_slug, $outer) = array_pad($match, 6, array('', 0)); list($all, $before, $at, $username, $list_slug, $outer) = array_pad($match, 6, ['', 0]);
$start_position = $at[1] > 0 ? StringUtils::strlen(substr($tweet, 0, $at[1])) : $at[1]; $start_position = $at[1] > 0 ? StringUtils::strlen(substr($tweet, 0, $at[1])) : $at[1];
$end_position = $start_position + StringUtils::strlen($at[0]) + StringUtils::strlen($username[0]); $end_position = $start_position + StringUtils::strlen($at[0]) + StringUtils::strlen($username[0]);
$entity = array( $entity = [
'screen_name' => $username[0], 'screen_name' => $username[0],
'list_slug' => $list_slug[0], 'list_slug' => $list_slug[0],
'indices' => array($start_position, $end_position), 'indices' => [$start_position, $end_position],
); ];
if (preg_match(self::$patterns['end_mention_match'], $outer[0])) { if (preg_match(self::$patterns['end_mention_match'], $outer[0])) {
continue; continue;
@ -485,7 +503,8 @@ class Extractor extends Regex
/** /**
* Extracts all the usernames and the indices they occur at from the tweet. * Extracts all the usernames and the indices they occur at from the tweet.
* *
* @return array The username elements in the tweet. * @return array The username elements in the tweet.
*
* @deprecated since version 1.1.0 * @deprecated since version 1.1.0
*/ */
public function extractMentionedUsernamesOrListsWithIndices() public function extractMentionedUsernamesOrListsWithIndices()
@ -494,9 +513,10 @@ class Extractor extends Regex
} }
/** /**
* setter/getter for extractURLWithoutProtocol * setter/getter for extractURLWithoutProtocol.
*
* @param bool $flag
* *
* @param boolean $flag
* @return Extractor * @return Extractor
*/ */
public function extractURLWithoutProtocol($flag = null) public function extractURLWithoutProtocol($flag = null)
@ -505,6 +525,7 @@ class Extractor extends Regex
return $this->extractURLWithoutProtocol; return $this->extractURLWithoutProtocol;
} }
$this->extractURLWithoutProtocol = (bool) $flag; $this->extractURLWithoutProtocol = (bool) $flag;
return $this; return $this;
} }
@ -513,12 +534,13 @@ class Extractor extends Regex
* This returns a new array with no overlapping entities. * This returns a new array with no overlapping entities.
* *
* @param array $entities * @param array $entities
*
* @return array * @return array
*/ */
public function removeOverlappingEntities($entities) public function removeOverlappingEntities($entities)
{ {
$result = array(); $result = [];
usort($entities, array($this, 'sortEntites')); usort($entities, [$this, 'sortEntites']);
$prev = null; $prev = null;
foreach ($entities as $entity) { foreach ($entities as $entity) {
@ -528,14 +550,16 @@ class Extractor extends Regex
$prev = $entity; $prev = $entity;
$result[] = $entity; $result[] = $entity;
} }
return $result; return $result;
} }
/** /**
* sort by entity start index * sort by entity start index.
* *
* @param array $a * @param array $a
* @param array $b * @param array $b
*
* @return int * @return int
*/ */
protected function sortEntites($a, $b) protected function sortEntites($a, $b)
@ -543,6 +567,7 @@ class Extractor extends Regex
if ($a['indices'][0] == $b['indices'][0]) { if ($a['indices'][0] == $b['indices'][0]) {
return 0; return 0;
} }
return ($a['indices'][0] < $b['indices'][0]) ? -1 : 1; return ($a['indices'][0] < $b['indices'][0]) ? -1 : 1;
} }
} }

View file

@ -2,35 +2,36 @@
namespace App\Util\Lexer; namespace App\Util\Lexer;
class Hashtag { class Hashtag
{
public static function getHashtags($status) public static function getHashtags($status)
{ {
$hashtags = false; $hashtags = false;
preg_match_all("/(?<!&)(#\w+)/u", $status, $matches); preg_match_all("/(?<!&)(#\w+)/u", $status, $matches);
if ($matches) { if ($matches) {
$res = array_count_values($matches[0]); $res = array_count_values($matches[0]);
$hashtags = array_keys($res); $hashtags = array_keys($res);
} }
return $hashtags;
}
public static function replaceHashtagsWithLinks($status) return $hashtags;
{
$hashtags = self::getHashtags($status);
if(!$hashtags) { return false; }
$rendered = $status;
foreach($hashtags as $hashtag) {
$tag = substr($hashtag, 1);
$link = config('routes.hashtag.search') . $tag;
$href = "<a href='{$link}' class='mention hashtag status-link' rel='noopener'>{$hashtag}</a>";
$rendered = str_replace($hashtag, $href, $rendered);
} }
return $rendered; public static function replaceHashtagsWithLinks($status)
{
$hashtags = self::getHashtags($status);
if (!$hashtags) {
return false;
}
} $rendered = $status;
} foreach ($hashtags as $hashtag) {
$tag = substr($hashtag, 1);
$link = config('routes.hashtag.search').$tag;
$href = "<a href='{$link}' class='mention hashtag status-link' rel='noopener'>{$hashtag}</a>";
$rendered = str_replace($hashtag, $href, $rendered);
}
return $rendered;
}
}

View file

@ -4,16 +4,12 @@
* @author Nick Pope <nick@nickpope.me.uk> * @author Nick Pope <nick@nickpope.me.uk>
* @copyright Copyright © 2010, Nick Pope * @copyright Copyright © 2010, Nick Pope
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter.Text
*/ */
namespace App\Util\Lexer; namespace App\Util\Lexer;
use App\Util\Lexer\Regex;
use App\Util\Lexer\StringUtils;
/** /**
* Twitter HitHighlighter Class * Twitter HitHighlighter Class.
* *
* Performs "hit highlighting" on tweets that have been auto-linked already. * Performs "hit highlighting" on tweets that have been auto-linked already.
* Useful with the results returned from the search API. * Useful with the results returned from the search API.
@ -25,27 +21,25 @@ use App\Util\Lexer\StringUtils;
* @author Nick Pope <nick@nickpope.me.uk> * @author Nick Pope <nick@nickpope.me.uk>
* @copyright Copyright © 2010, Nick Pope * @copyright Copyright © 2010, Nick Pope
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter.Text
*/ */
class HitHighlighter extends Regex class HitHighlighter extends Regex
{ {
/** /**
* The tag to surround hits with. * The tag to surround hits with.
* *
* @var string * @var string
*/ */
protected $tag = 'em'; protected $tag = 'em';
/** /**
* Provides fluent method chaining. * Provides fluent method chaining.
* *
* @param string $tweet The tweet to be hit highlighted. * @param string $tweet The tweet to be hit highlighted.
* @param bool $full_encode Whether to encode all special characters. * @param bool $full_encode Whether to encode all special characters.
* *
* @see __construct() * @see __construct()
* *
* @return HitHighlighter * @return HitHighlighter
*/ */
public static function create($tweet = null, $full_encode = false) public static function create($tweet = null, $full_encode = false)
{ {
@ -59,9 +53,9 @@ class HitHighlighter extends Regex
* *
* @see htmlspecialchars() * @see htmlspecialchars()
* *
* @param string $tweet The tweet to be hit highlighted. * @param string $tweet The tweet to be hit highlighted.
* @param bool $escape Whether to escape the tweet (default: true). * @param bool $escape Whether to escape the tweet (default: true).
* @param bool $full_encode Whether to encode all special characters. * @param bool $full_encode Whether to encode all special characters.
*/ */
public function __construct($tweet = null, $escape = true, $full_encode = false) public function __construct($tweet = null, $escape = true, $full_encode = false)
{ {
@ -79,7 +73,7 @@ class HitHighlighter extends Regex
/** /**
* Set the highlighting tag to surround hits with. The default tag is 'em'. * Set the highlighting tag to surround hits with. The default tag is 'em'.
* *
* @return string The tag name. * @return string The tag name.
*/ */
public function getTag() public function getTag()
{ {
@ -89,26 +83,27 @@ class HitHighlighter extends Regex
/** /**
* Set the highlighting tag to surround hits with. The default tag is 'em'. * Set the highlighting tag to surround hits with. The default tag is 'em'.
* *
* @param string $v The tag name. * @param string $v The tag name.
* *
* @return HitHighlighter Fluid method chaining. * @return HitHighlighter Fluid method chaining.
*/ */
public function setTag($v) public function setTag($v)
{ {
$this->tag = $v; $this->tag = $v;
return $this; return $this;
} }
/** /**
* Hit highlights the tweet. * Hit highlights the tweet.
* *
* @param string $tweet The tweet to be hit highlighted. * @param string $tweet The tweet to be hit highlighted.
* @param array $hits An array containing the start and end index pairs * @param array $hits An array containing the start and end index pairs
* for the highlighting. * for the highlighting.
* @param bool $escape Whether to escape the tweet (default: true). * @param bool $escape Whether to escape the tweet (default: true).
* @param bool $full_encode Whether to encode all special characters. * @param bool $full_encode Whether to encode all special characters.
* *
* @return string The hit highlighted tweet. * @return string The hit highlighted tweet.
*/ */
public function highlight($tweet = null, array $hits = null) public function highlight($tweet = null, array $hits = null)
{ {
@ -119,8 +114,8 @@ class HitHighlighter extends Regex
return $tweet; return $tweet;
} }
$highlightTweet = ''; $highlightTweet = '';
$tags = array('<' . $this->tag . '>', '</' . $this->tag . '>'); $tags = ['<'.$this->tag.'>', '</'.$this->tag.'>'];
# Check whether we can simply replace or whether we need to chunk... // Check whether we can simply replace or whether we need to chunk...
if (strpos($tweet, '<') === false) { if (strpos($tweet, '<') === false) {
$ti = 0; // tag increment (for added tags) $ti = 0; // tag increment (for added tags)
$highlightTweet = $tweet; $highlightTweet = $tweet;
@ -137,12 +132,12 @@ class HitHighlighter extends Regex
$chunk_cursor = 0; $chunk_cursor = 0;
$offset = 0; $offset = 0;
$start_in_chunk = false; $start_in_chunk = false;
# Flatten the multidimensional hits array: // Flatten the multidimensional hits array:
$hits_flat = array(); $hits_flat = [];
foreach ($hits as $hit) { foreach ($hits as $hit) {
$hits_flat = array_merge($hits_flat, $hit); $hits_flat = array_merge($hits_flat, $hit);
} }
# Loop over the hit indices: // Loop over the hit indices:
for ($index = 0; $index < count($hits_flat); $index++) { for ($index = 0; $index < count($hits_flat); $index++) {
$hit = $hits_flat[$index]; $hit = $hits_flat[$index];
$tag = $tags[$index % 2]; $tag = $tags[$index % 2];
@ -154,7 +149,7 @@ class HitHighlighter extends Regex
$placed = true; $placed = true;
} }
if (isset($chunks[$chunk_index + 1])) { if (isset($chunks[$chunk_index + 1])) {
$highlightTweet .= '<' . $chunks[$chunk_index + 1] . '>'; $highlightTweet .= '<'.$chunks[$chunk_index + 1].'>';
} }
$offset += StringUtils::strlen($chunk); $offset += StringUtils::strlen($chunk);
$chunk_cursor = 0; $chunk_cursor = 0;
@ -164,12 +159,12 @@ class HitHighlighter extends Regex
} }
if (!$placed && $chunk !== null) { if (!$placed && $chunk !== null) {
$hit_spot = $hit - $offset; $hit_spot = $hit - $offset;
$highlightTweet .= StringUtils::substr($chunk, $chunk_cursor, $hit_spot - $chunk_cursor) . $tag; $highlightTweet .= StringUtils::substr($chunk, $chunk_cursor, $hit_spot - $chunk_cursor).$tag;
$chunk_cursor = $hit_spot; $chunk_cursor = $hit_spot;
$start_in_chunk = ($index % 2 === 0); $start_in_chunk = ($index % 2 === 0);
$placed = true; $placed = true;
} }
# Ultimate fallback - hits that run off the end get a closing tag: // Ultimate fallback - hits that run off the end get a closing tag:
if (!$placed) { if (!$placed) {
$highlightTweet .= $tag; $highlightTweet .= $tag;
} }
@ -179,20 +174,22 @@ class HitHighlighter extends Regex
$highlightTweet .= StringUtils::substr($chunk, $chunk_cursor); $highlightTweet .= StringUtils::substr($chunk, $chunk_cursor);
} }
for ($index = $chunk_index + 1; $index < count($chunks); $index++) { for ($index = $chunk_index + 1; $index < count($chunks); $index++) {
$highlightTweet .= ($index % 2 === 0 ? $chunks[$index] : '<' . $chunks[$index] . '>'); $highlightTweet .= ($index % 2 === 0 ? $chunks[$index] : '<'.$chunks[$index].'>');
} }
} }
} }
return $highlightTweet; return $highlightTweet;
} }
/** /**
* Hit highlights the tweet. * Hit highlights the tweet.
* *
* @param array $hits An array containing the start and end index pairs * @param array $hits An array containing the start and end index pairs
* for the highlighting. * for the highlighting.
*
* @return string The hit highlighted tweet.
* *
* @return string The hit highlighted tweet.
* @deprecated since version 1.1.0 * @deprecated since version 1.1.0
*/ */
public function addHitHighlighting(array $hits) public function addHitHighlighting(array $hits)

View file

@ -6,15 +6,12 @@
* @author Takashi Nojima * @author Takashi Nojima
* @copyright Copyright 2014 Mike Cochrane, Nick Pope, Takashi Nojima * @copyright Copyright 2014 Mike Cochrane, Nick Pope, Takashi Nojima
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter.Text
*/ */
namespace App\Util\Lexer; namespace App\Util\Lexer;
use App\Util\Lexer\Autolink;
/** /**
* Twitter LooseAutolink Class * Twitter LooseAutolink Class.
* *
* Parses tweets and generates HTML anchor tags around URLs, usernames, * Parses tweets and generates HTML anchor tags around URLs, usernames,
* username/list pairs and hashtags. * username/list pairs and hashtags.
@ -28,18 +25,19 @@ use App\Util\Lexer\Autolink;
* @author Takashi Nojima * @author Takashi Nojima
* @copyright Copyright 2014 Mike Cochrane, Nick Pope, Takashi Nojima * @copyright Copyright 2014 Mike Cochrane, Nick Pope, Takashi Nojima
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter.Text *
* @since 1.8.0 * @since 1.8.0
* @deprecated since version 1.9.0 * @deprecated since version 1.9.0
*/ */
class LooseAutolink extends Autolink class LooseAutolink extends Autolink
{ {
/** /**
* Auto-link hashtags, URLs, usernames and lists. * Auto-link hashtags, URLs, usernames and lists.
* *
* @param string The tweet to be converted * @param string The tweet to be converted
*
* @return string that auto-link HTML added * @return string that auto-link HTML added
*
* @deprecated since version 1.9.0 * @deprecated since version 1.9.0
*/ */
public function autoLink($tweet = null) public function autoLink($tweet = null)
@ -47,6 +45,7 @@ class LooseAutolink extends Autolink
if (!is_null($tweet)) { if (!is_null($tweet)) {
$this->tweet = $tweet; $this->tweet = $tweet;
} }
return $this->addLinks(); return $this->addLinks();
} }
@ -62,6 +61,7 @@ class LooseAutolink extends Autolink
if (!is_null($tweet)) { if (!is_null($tweet)) {
$this->tweet = $tweet; $this->tweet = $tweet;
} }
return $this->addLinksToUsernamesAndLists(); return $this->addLinksToUsernamesAndLists();
} }
@ -76,6 +76,7 @@ class LooseAutolink extends Autolink
if (!is_null($tweet)) { if (!is_null($tweet)) {
$this->tweet = $tweet; $this->tweet = $tweet;
} }
return $this->addLinksToHashtags(); return $this->addLinksToHashtags();
} }
@ -91,6 +92,7 @@ class LooseAutolink extends Autolink
if (!is_null($tweet)) { if (!is_null($tweet)) {
$this->tweet = $tweet; $this->tweet = $tweet;
} }
return $this->addLinksToURLs(); return $this->addLinksToURLs();
} }
@ -105,13 +107,15 @@ class LooseAutolink extends Autolink
if (!is_null($tweet)) { if (!is_null($tweet)) {
$this->tweet = $tweet; $this->tweet = $tweet;
} }
return $this->addLinksToCashtags(); return $this->addLinksToCashtags();
} }
/** /**
* Adds links to all elements in the tweet. * Adds links to all elements in the tweet.
* *
* @return string The modified tweet. * @return string The modified tweet.
*
* @deprecated since version 1.9.0 * @deprecated since version 1.9.0
*/ */
public function addLinks() public function addLinks()
@ -123,19 +127,20 @@ class LooseAutolink extends Autolink
$this->tweet = $this->addLinksToUsernamesAndLists(); $this->tweet = $this->addLinksToUsernamesAndLists();
$modified = $this->tweet; $modified = $this->tweet;
$this->tweet = $original; $this->tweet = $original;
return $modified; return $modified;
} }
/** /**
* Adds links to hashtag elements in the tweet. * Adds links to hashtag elements in the tweet.
* *
* @return string The modified tweet. * @return string The modified tweet.
*/ */
public function addLinksToHashtags() public function addLinksToHashtags()
{ {
return preg_replace_callback( return preg_replace_callback(
self::$patterns['valid_hashtag'], self::$patterns['valid_hashtag'],
array($this, '_addLinksToHashtags'), [$this, '_addLinksToHashtags'],
$this->tweet $this->tweet
); );
} }
@ -143,13 +148,13 @@ class LooseAutolink extends Autolink
/** /**
* Adds links to cashtag elements in the tweet. * Adds links to cashtag elements in the tweet.
* *
* @return string The modified tweet. * @return string The modified tweet.
*/ */
public function addLinksToCashtags() public function addLinksToCashtags()
{ {
return preg_replace_callback( return preg_replace_callback(
self::$patterns['valid_cashtag'], self::$patterns['valid_cashtag'],
array($this, '_addLinksToCashtags'), [$this, '_addLinksToCashtags'],
$this->tweet $this->tweet
); );
} }
@ -157,23 +162,23 @@ class LooseAutolink extends Autolink
/** /**
* Adds links to URL elements in the tweet. * Adds links to URL elements in the tweet.
* *
* @return string The modified tweet * @return string The modified tweet
*/ */
public function addLinksToURLs() public function addLinksToURLs()
{ {
return preg_replace_callback(self::$patterns['valid_url'], array($this, '_addLinksToURLs'), $this->tweet); return preg_replace_callback(self::$patterns['valid_url'], [$this, '_addLinksToURLs'], $this->tweet);
} }
/** /**
* Adds links to username/list elements in the tweet. * Adds links to username/list elements in the tweet.
* *
* @return string The modified tweet. * @return string The modified tweet.
*/ */
public function addLinksToUsernamesAndLists() public function addLinksToUsernamesAndLists()
{ {
return preg_replace_callback( return preg_replace_callback(
self::$patterns['valid_mentions_or_lists'], self::$patterns['valid_mentions_or_lists'],
array($this, '_addLinksToUsernamesAndLists'), [$this, '_addLinksToUsernamesAndLists'],
$this->tweet $this->tweet
); );
} }
@ -183,21 +188,22 @@ class LooseAutolink extends Autolink
* *
* This is a helper function to perform the generation of the link. * This is a helper function to perform the generation of the link.
* *
* @param string $url The URL to use as the href. * @param string $url The URL to use as the href.
* @param string $class The CSS class(es) to apply (space separated). * @param string $class The CSS class(es) to apply (space separated).
* @param string $element The tweet element to wrap. * @param string $element The tweet element to wrap.
*
* @return string The tweet element with a link applied.
* *
* @return string The tweet element with a link applied.
* @deprecated since version 1.1.0 * @deprecated since version 1.1.0
*/ */
protected function wrap($url, $class, $element) protected function wrap($url, $class, $element)
{ {
$link = '<a'; $link = '<a';
if ($class) { if ($class) {
$link .= ' class="' . $class . '"'; $link .= ' class="'.$class.'"';
} }
$link .= ' href="' . $url . '"'; $link .= ' href="'.$url.'"';
$rel = array(); $rel = [];
if ($this->external) { if ($this->external) {
$rel[] = 'external'; $rel[] = 'external';
} }
@ -205,12 +211,13 @@ class LooseAutolink extends Autolink
$rel[] = 'nofollow'; $rel[] = 'nofollow';
} }
if (!empty($rel)) { if (!empty($rel)) {
$link .= ' rel="' . implode(' ', $rel) . '"'; $link .= ' rel="'.implode(' ', $rel).'"';
} }
if ($this->target) { if ($this->target) {
$link .= ' target="' . $this->target . '"'; $link .= ' target="'.$this->target.'"';
} }
$link .= '>' . $element . '</a>'; $link .= '>'.$element.'</a>';
return $link; return $link;
} }
@ -219,22 +226,22 @@ class LooseAutolink extends Autolink
* *
* This is a helper function to perform the generation of the hashtag link. * This is a helper function to perform the generation of the hashtag link.
* *
* @param string $url The URL to use as the href. * @param string $url The URL to use as the href.
* @param string $class The CSS class(es) to apply (space separated). * @param string $class The CSS class(es) to apply (space separated).
* @param string $element The tweet element to wrap. * @param string $element The tweet element to wrap.
* *
* @return string The tweet element with a link applied. * @return string The tweet element with a link applied.
*/ */
protected function wrapHash($url, $class, $element) protected function wrapHash($url, $class, $element)
{ {
$title = preg_replace('//u', '#', $element); $title = preg_replace('//u', '#', $element);
$link = '<a'; $link = '<a';
$link .= ' href="' . $url . '"'; $link .= ' href="'.$url.'"';
$link .= ' title="' . $title . '"'; $link .= ' title="'.$title.'"';
if ($class) { if ($class) {
$link .= ' class="' . $class . '"'; $link .= ' class="'.$class.'"';
} }
$rel = array(); $rel = [];
if ($this->external) { if ($this->external) {
$rel[] = 'external'; $rel[] = 'external';
} }
@ -242,12 +249,13 @@ class LooseAutolink extends Autolink
$rel[] = 'nofollow'; $rel[] = 'nofollow';
} }
if (!empty($rel)) { if (!empty($rel)) {
$link .= ' rel="' . implode(' ', $rel) . '"'; $link .= ' rel="'.implode(' ', $rel).'"';
} }
if ($this->target) { if ($this->target) {
$link .= ' target="' . $this->target . '"'; $link .= ' target="'.$this->target.'"';
} }
$link .= '>' . $element . '</a>'; $link .= '>'.$element.'</a>';
return $link; return $link;
} }
@ -255,8 +263,10 @@ class LooseAutolink extends Autolink
* Callback used by the method that adds links to hashtags. * Callback used by the method that adds links to hashtags.
* *
* @see addLinksToHashtags() * @see addLinksToHashtags()
* @param array $matches The regular expression matches. *
* @return string The link-wrapped hashtag. * @param array $matches The regular expression matches.
*
* @return string The link-wrapped hashtag.
*/ */
protected function _addLinksToHashtags($matches) protected function _addLinksToHashtags($matches)
{ {
@ -266,13 +276,14 @@ class LooseAutolink extends Autolink
return $all; return $all;
} }
$replacement = $before; $replacement = $before;
$element = $hash . $tag; $element = $hash.$tag;
$url = $this->url_base_hash . $tag; $url = $this->url_base_hash.$tag;
$class_hash = $this->class_hash; $class_hash = $this->class_hash;
if (preg_match(self::$patterns['rtl_chars'], $element)) { if (preg_match(self::$patterns['rtl_chars'], $element)) {
$class_hash .= ' rtl'; $class_hash .= ' rtl';
} }
$replacement .= $this->wrapHash($url, $class_hash, $element); $replacement .= $this->wrapHash($url, $class_hash, $element);
return $replacement; return $replacement;
} }
@ -280,8 +291,10 @@ class LooseAutolink extends Autolink
* Callback used by the method that adds links to cashtags. * Callback used by the method that adds links to cashtags.
* *
* @see addLinksToCashtags() * @see addLinksToCashtags()
* @param array $matches The regular expression matches. *
* @return string The link-wrapped cashtag. * @param array $matches The regular expression matches.
*
* @return string The link-wrapped cashtag.
*/ */
protected function _addLinksToCashtags($matches) protected function _addLinksToCashtags($matches)
{ {
@ -291,9 +304,10 @@ class LooseAutolink extends Autolink
return $all; return $all;
} }
$replacement = $before; $replacement = $before;
$element = $cash . $tag; $element = $cash.$tag;
$url = $this->url_base_cash . $tag; $url = $this->url_base_cash.$tag;
$replacement .= $this->wrapHash($url, $this->class_cash, $element); $replacement .= $this->wrapHash($url, $this->class_cash, $element);
return $replacement; return $replacement;
} }
@ -301,8 +315,10 @@ class LooseAutolink extends Autolink
* Callback used by the method that adds links to URLs. * Callback used by the method that adds links to URLs.
* *
* @see addLinksToURLs() * @see addLinksToURLs()
* @param array $matches The regular expression matches. *
* @return string The link-wrapped URL. * @param array $matches The regular expression matches.
*
* @return string The link-wrapped URL.
*/ */
protected function _addLinksToURLs($matches) protected function _addLinksToURLs($matches)
{ {
@ -311,38 +327,41 @@ class LooseAutolink extends Autolink
if (!$protocol) { if (!$protocol) {
return $all; return $all;
} }
return $before . $this->wrap($url, $this->class_url, $url);
return $before.$this->wrap($url, $this->class_url, $url);
} }
/** /**
* Callback used by the method that adds links to username/list pairs. * Callback used by the method that adds links to username/list pairs.
* *
* @see addLinksToUsernamesAndLists() * @see addLinksToUsernamesAndLists()
* @param array $matches The regular expression matches. *
* @return string The link-wrapped username/list pair. * @param array $matches The regular expression matches.
*
* @return string The link-wrapped username/list pair.
*/ */
protected function _addLinksToUsernamesAndLists($matches) protected function _addLinksToUsernamesAndLists($matches)
{ {
list($all, $before, $at, $username, $slash_listname, $after) = array_pad($matches, 6, ''); list($all, $before, $at, $username, $slash_listname, $after) = array_pad($matches, 6, '');
# If $after is not empty, there is an invalid character. // If $after is not empty, there is an invalid character.
if (!empty($slash_listname)) { if (!empty($slash_listname)) {
# Replace the list and username // Replace the list and username
$element = $username . $slash_listname; $element = $username.$slash_listname;
$class = $this->class_list; $class = $this->class_list;
$url = $this->url_base_list . $element; $url = $this->url_base_list.$element;
} else { } else {
if (preg_match(self::$patterns['end_mention_match'], $after)) { if (preg_match(self::$patterns['end_mention_match'], $after)) {
return $all; return $all;
} }
# Replace the username // Replace the username
$element = $username; $element = $username;
$class = $this->class_user; $class = $this->class_user;
$url = $this->url_base_user . $element; $url = $this->url_base_user.$element;
} }
# XXX: Due to use of preg_replace_callback() for multiple replacements in a // XXX: Due to use of preg_replace_callback() for multiple replacements in a
# single tweet and also as only the match is replaced and we have to // single tweet and also as only the match is replaced and we have to
# use a look-ahead for $after because there is no equivalent for the // use a look-ahead for $after because there is no equivalent for the
# $' (dollar apostrophe) global from Ruby, we MUST NOT append $after. // $' (dollar apostrophe) global from Ruby, we MUST NOT append $after.
return $before . $at . $this->wrap($url, $class, $element); return $before.$at.$this->wrap($url, $class, $element);
} }
} }

View file

@ -2,41 +2,41 @@
namespace App\Util\Lexer; namespace App\Util\Lexer;
class Nickname { class Nickname
{
public static function normalizeProfileUrl($url) public static function normalizeProfileUrl($url)
{ {
if(starts_with($url, 'acct:')) { if (starts_with($url, 'acct:')) {
$url = str_replace('acct:', '', $url); $url = str_replace('acct:', '', $url);
} }
if(!str_contains($url, '@') && filter_var($url, FILTER_VALIDATE_URL)) { if (!str_contains($url, '@') && filter_var($url, FILTER_VALIDATE_URL)) {
$parsed = parse_url($url); $parsed = parse_url($url);
$username = str_replace(['/','\\','@'], '', $parsed['path']); $username = str_replace(['/', '\\', '@'], '', $parsed['path']);
return ['domain' => $parsed['host'], 'username' => $username];
}
$parts = explode('@', $url);
$username = null;
$domain = null;
foreach ($parts as $part) { return ['domain' => $parsed['host'], 'username' => $username];
}
$parts = explode('@', $url);
$username = null;
$domain = null;
foreach ($parts as $part) {
// skip empty array slices // skip empty array slices
if(empty($part)) { if (empty($part)) {
continue; continue;
} }
// if slice contains . assume its a domain // if slice contains . assume its a domain
if(str_contains($part, '.')) { if (str_contains($part, '.')) {
$domain = filter_var($part, FILTER_VALIDATE_URL) ? $domain = filter_var($part, FILTER_VALIDATE_URL) ?
parse_url($part, PHP_URL_HOST) : parse_url($part, PHP_URL_HOST) :
$part; $part;
} else { } else {
$username = $part; $username = $part;
}
} }
} return ['domain' => $domain, 'username' => $username];
return ['domain' => $domain, 'username' => $username];
} }
}
}

View file

@ -2,35 +2,38 @@
namespace App\Util\Lexer; namespace App\Util\Lexer;
class PrettyNumber { class PrettyNumber
{
public static function convert($expression)
{
$abbrevs = [12 => 'T', 9 => 'B', 6 => 'M', 3 => 'K', 0 => ''];
foreach ($abbrevs as $exponent => $abbrev) {
if ($expression >= pow(10, $exponent)) {
$display_num = $expression / pow(10, $exponent);
$num = number_format($display_num, 0).$abbrev;
public static function convert($expression) return $num;
{ }
$abbrevs = array(12 => "T", 9 => "B", 6 => "M", 3 => "K", 0 => ""); }
foreach($abbrevs as $exponent => $abbrev) {
if($expression >= pow(10, $exponent)) {
$display_num = $expression / pow(10, $exponent);
$num = number_format($display_num,0) . $abbrev;
return $num;
}
}
return $expression;
}
public static function size($expression, $kb = false) return $expression;
{ }
if($kb) {
$expression = $expression * 1024;
}
$size = intval($expression);
$precision = 0;
$short = true;
$units = $short ?
['B','k','M','G','T','P','E','Z','Y'] :
['B','kB','MB','GB','TB','PB','EB','ZB','YB'];
for($i = 0; ($size / 1024) > 0.9; $i++, $size /= 1024) {}
$res = round($size, $precision).$units[$i];
return $res;
}
} public static function size($expression, $kb = false)
{
if ($kb) {
$expression = $expression * 1024;
}
$size = intval($expression);
$precision = 0;
$short = true;
$units = $short ?
['B', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] :
['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
for ($i = 0; ($size / 1024) > 0.9; $i++, $size /= 1024) {
}
$res = round($size, $precision).$units[$i];
return $res;
}
}

File diff suppressed because one or more lines are too long

View file

@ -2,180 +2,179 @@
namespace App\Util\Lexer; namespace App\Util\Lexer;
class RestrictedNames { class RestrictedNames
{
static $blacklist = [ public static $blacklist = [
"about", 'about',
"abuse", 'abuse',
"administrator", 'administrator',
"app", 'app',
"autoconfig", 'autoconfig',
"blog", 'blog',
"broadcasthost", 'broadcasthost',
"community", 'community',
"contact", 'contact',
"contact-us", 'contact-us',
"contact_us", 'contact_us',
"copyright", 'copyright',
"d", 'd',
"dashboard", 'dashboard',
"dev", 'dev',
"developer", 'developer',
"developers", 'developers',
"discover", 'discover',
"discovers", 'discovers',
"doc", 'doc',
"docs", 'docs',
"download", 'download',
"domainadmin", 'domainadmin',
"domainadministrator", 'domainadministrator',
"email", 'email',
"errors", 'errors',
"events", 'events',
"example", 'example',
"faq", 'faq',
"faqs", 'faqs',
"features", 'features',
"ftp", 'ftp',
"guest", 'guest',
"guests", 'guests',
"help", 'help',
"hostmaster", 'hostmaster',
"hostmaster", 'hostmaster',
"image", 'image',
"images", 'images',
"imap", 'imap',
"img", 'img',
"info", 'info',
"info", 'info',
"is", 'is',
"isatap", 'isatap',
"it", 'it',
"localdomain", 'localdomain',
"localhost", 'localhost',
"mail", 'mail',
"mailer-daemon", 'mailer-daemon',
"mailerdaemon", 'mailerdaemon',
"marketing", 'marketing',
"me", 'me',
"media", 'media',
"mis", 'mis',
"mx", 'mx',
"new", 'new',
"news", 'news',
"news", 'news',
"no-reply", 'no-reply',
"nobody", 'nobody',
"noc", 'noc',
"noreply", 'noreply',
"ns0", 'ns0',
"ns1", 'ns1',
"ns2", 'ns2',
"ns3", 'ns3',
"ns4", 'ns4',
"ns5", 'ns5',
"ns6", 'ns6',
"ns7", 'ns7',
"ns8", 'ns8',
"ns9", 'ns9',
"owner", 'owner',
"pop", 'pop',
"pop3", 'pop3',
"postmaster", 'postmaster',
"pricing", 'pricing',
"privacy", 'privacy',
"root", 'root',
"sales", 'sales',
"security", 'security',
"signin", 'signin',
"signout", 'signout',
"smtp", 'smtp',
"src", 'src',
"ssladmin", 'ssladmin',
"ssladministrator", 'ssladministrator',
"sslwebmaster", 'sslwebmaster',
"status", 'status',
"support", 'support',
"support", 'support',
"sys", 'sys',
"sysadmin", 'sysadmin',
"system", 'system',
"terms", 'terms',
"tutorial", 'tutorial',
"tutorials", 'tutorials',
"usenet", 'usenet',
"uucp", 'uucp',
"webmaster", 'webmaster',
"wpad", 'wpad',
"www" 'www',
]; ];
static $reserved = [ public static $reserved = [
// Reserved for instance admin // Reserved for instance admin
"admin", 'admin',
// Static Assets // Static Assets
"assets", 'assets',
"storage", 'storage',
// Laravel Horizon // Laravel Horizon
"horizon", 'horizon',
// Reserved routes // Reserved routes
"account", 'account',
"api", 'api',
"auth", 'auth',
"css", 'css',
"c", 'c',
"i", 'i',
"dashboard", 'dashboard',
"deck", 'deck',
"discover", 'discover',
"docs", 'docs',
"fonts", 'fonts',
"home", 'home',
"img", 'img',
"js", 'js',
"login", 'login',
"logout", 'logout',
"media", 'media',
"p", 'p',
"password", 'password',
"report", 'report',
"reports", 'reports',
"search", 'search',
"settings", 'settings',
"statuses", 'statuses',
"site", 'site',
"sites", 'sites',
"timeline", 'timeline',
"timelines", 'timelines',
"tour", 'tour',
"user", 'user',
"users", 'users',
"vendor", 'vendor',
"400", '400',
"401", '401',
"403", '403',
"404", '404',
"500", '500',
"503", '503',
"504", '504',
]; ];
public static function get() public static function get()
{ {
$reserved = $blacklist = [];
$reserved = $blacklist = [];
if(true == config('pixelfed.restricted_names.use_blacklist')) { if (true == config('pixelfed.restricted_names.use_blacklist')) {
$blacklist = self::$blacklist; $blacklist = self::$blacklist;
} }
if(true == config('pixelfed.restricted_names.reserved_routes')) { if (true == config('pixelfed.restricted_names.reserved_routes')) {
$reserved = self::$reserved; $reserved = self::$reserved;
} }
return array_merge($blacklist, $reserved);
}
} return array_merge($blacklist, $reserved);
}
}

View file

@ -4,29 +4,27 @@
* @author Takashi Nojima * @author Takashi Nojima
* @copyright Copyright 2014, Takashi Nojima * @copyright Copyright 2014, Takashi Nojima
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter.Text
*/ */
namespace App\Util\Lexer; namespace App\Util\Lexer;
/** /**
* String utility * String utility.
* *
* @author Takashi Nojima * @author Takashi Nojima
* @copyright Copyright 2014, Takashi Nojima * @copyright Copyright 2014, Takashi Nojima
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter
*/ */
class StringUtils class StringUtils
{ {
/** /**
* alias of mb_substr * alias of mb_substr.
* *
* @param string $str * @param string $str
* @param integer $start * @param int $start
* @param integer $length * @param int $length
* @param string $encoding * @param string $encoding
*
* @return string * @return string
*/ */
public static function substr($str, $start, $length = null, $encoding = 'UTF-8') public static function substr($str, $start, $length = null, $encoding = 'UTF-8')
@ -35,15 +33,17 @@ class StringUtils
// for PHP <= 5.4.7 // for PHP <= 5.4.7
$length = mb_strlen($str, $encoding); $length = mb_strlen($str, $encoding);
} }
return mb_substr($str, $start, $length, $encoding); return mb_substr($str, $start, $length, $encoding);
} }
/** /**
* alias of mb_strlen * alias of mb_strlen.
* *
* @param string $str * @param string $str
* @param string $encoding * @param string $encoding
* @return integer *
* @return int
*/ */
public static function strlen($str, $encoding = 'UTF-8') public static function strlen($str, $encoding = 'UTF-8')
{ {
@ -51,13 +51,14 @@ class StringUtils
} }
/** /**
* alias of mb_strpos * alias of mb_strpos.
* *
* @param string $haystack * @param string $haystack
* @param string $needle * @param string $needle
* @param integer $offset * @param int $offset
* @param string $encoding * @param string $encoding
* @return integer *
* @return int
*/ */
public static function strpos($haystack, $needle, $offset = 0, $encoding = 'UTF-8') public static function strpos($haystack, $needle, $offset = 0, $encoding = 'UTF-8')
{ {
@ -67,13 +68,13 @@ class StringUtils
/** /**
* A multibyte-aware substring replacement function. * A multibyte-aware substring replacement function.
* *
* @param string $string The string to modify. * @param string $string The string to modify.
* @param string $replacement The replacement string. * @param string $replacement The replacement string.
* @param int $start The start of the replacement. * @param int $start The start of the replacement.
* @param int $length The number of characters to replace. * @param int $length The number of characters to replace.
* @param string $encoding The encoding of the string. * @param string $encoding The encoding of the string.
* *
* @return string The modified string. * @return string The modified string.
* *
* @see http://www.php.net/manual/en/function.substr-replace.php#90146 * @see http://www.php.net/manual/en/function.substr-replace.php#90146
*/ */
@ -97,8 +98,10 @@ class StringUtils
$suffixOffset = $start + $length; $suffixOffset = $start + $length;
$suffixLength = $string_length - $start - $length; $suffixLength = $string_length - $start - $length;
return static::substr($string, 0, $start, $encoding) . $replacement . static::substr($string, $suffixOffset, $suffixLength, $encoding);
return static::substr($string, 0, $start, $encoding).$replacement.static::substr($string, $suffixOffset, $suffixLength, $encoding);
} }
return (is_null($length) === true) ? substr_replace($string, $replacement, $start) : substr_replace($string, $replacement, $start, $length); return (is_null($length) === true) ? substr_replace($string, $replacement, $start) : substr_replace($string, $replacement, $start, $length);
} }
} }

View file

@ -4,17 +4,12 @@
* @author Nick Pope <nick@nickpope.me.uk> * @author Nick Pope <nick@nickpope.me.uk>
* @copyright Copyright © 2010, Nick Pope * @copyright Copyright © 2010, Nick Pope
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter.Text
*/ */
namespace App\Util\Lexer; namespace App\Util\Lexer;
use App\Util\Lexer\Regex;
use App\Util\Lexer\Extractor;
use App\Util\Lexer\StringUtils;
/** /**
* Twitter Validator Class * Twitter Validator Class.
* *
* Performs "validation" on tweets. * Performs "validation" on tweets.
* *
@ -25,34 +20,31 @@ use App\Util\Lexer\StringUtils;
* @author Nick Pope <nick@nickpope.me.uk> * @author Nick Pope <nick@nickpope.me.uk>
* @copyright Copyright © 2010, Nick Pope * @copyright Copyright © 2010, Nick Pope
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter.Text
*/ */
class Validator extends Regex class Validator extends Regex
{ {
/** /**
* The maximum length of a tweet. * The maximum length of a tweet.
* *
* @var int * @var int
*/ */
const MAX_LENGTH = 140; const MAX_LENGTH = 140;
/** /**
* The length of a short URL beginning with http: * The length of a short URL beginning with http:.
* *
* @var int * @var int
*/ */
protected $short_url_length = 23; protected $short_url_length = 23;
/** /**
* The length of a short URL beginning with http: * The length of a short URL beginning with http:.
* *
* @var int * @var int
*/ */
protected $short_url_length_https = 23; protected $short_url_length_https = 23;
/** /**
*
* @var Extractor * @var Extractor
*/ */
protected $extractor = null; protected $extractor = null;
@ -60,12 +52,12 @@ class Validator extends Regex
/** /**
* Provides fluent method chaining. * Provides fluent method chaining.
* *
* @param string $tweet The tweet to be validated. * @param string $tweet The tweet to be validated.
* @param mixed $config Setup short URL length from Twitter API /help/configuration response. * @param mixed $config Setup short URL length from Twitter API /help/configuration response.
* *
* @see __construct() * @see __construct()
* *
* @return Validator * @return Validator
*/ */
public static function create($tweet = null, $config = null) public static function create($tweet = null, $config = null)
{ {
@ -75,7 +67,7 @@ class Validator extends Regex
/** /**
* Reads in a tweet to be parsed and validates it. * Reads in a tweet to be parsed and validates it.
* *
* @param string $tweet The tweet to validate. * @param string $tweet The tweet to validate.
*/ */
public function __construct($tweet = null, $config = null) public function __construct($tweet = null, $config = null)
{ {
@ -87,10 +79,12 @@ class Validator extends Regex
} }
/** /**
* Setup short URL length from Twitter API /help/configuration response * Setup short URL length from Twitter API /help/configuration response.
* *
* @param mixed $config * @param mixed $config
*
* @return Validator * @return Validator
*
* @link https://dev.twitter.com/docs/api/1/get/help/configuration * @link https://dev.twitter.com/docs/api/1/get/help/configuration
*/ */
public function setConfiguration($config) public function setConfiguration($config)
@ -117,19 +111,21 @@ class Validator extends Regex
} }
/** /**
* Set the length of a short URL beginning with http: * Set the length of a short URL beginning with http:.
* *
* @param mixed $length * @param mixed $length
*
* @return Validator * @return Validator
*/ */
public function setShortUrlLength($length) public function setShortUrlLength($length)
{ {
$this->short_url_length = intval($length); $this->short_url_length = intval($length);
return $this; return $this;
} }
/** /**
* Get the length of a short URL beginning with http: * Get the length of a short URL beginning with http:.
* *
* @return int * @return int
*/ */
@ -139,19 +135,21 @@ class Validator extends Regex
} }
/** /**
* Set the length of a short URL beginning with https: * Set the length of a short URL beginning with https:.
* *
* @param mixed $length * @param mixed $length
*
* @return Validator * @return Validator
*/ */
public function setShortUrlLengthHttps($length) public function setShortUrlLengthHttps($length)
{ {
$this->short_url_length_https = intval($length); $this->short_url_length_https = intval($length);
return $this; return $this;
} }
/** /**
* Get the length of a short URL beginning with https: * Get the length of a short URL beginning with https:.
* *
* @return int * @return int
*/ */
@ -164,7 +162,8 @@ class Validator extends Regex
* Check whether a tweet is valid. * Check whether a tweet is valid.
* *
* @param string $tweet The tweet to validate. * @param string $tweet The tweet to validate.
* @return boolean Whether the tweet is valid. *
* @return bool Whether the tweet is valid.
*/ */
public function isValidTweetText($tweet = null) public function isValidTweetText($tweet = null)
{ {
@ -181,13 +180,15 @@ class Validator extends Regex
if (preg_match(self::$patterns['invalid_characters'], $tweet)) { if (preg_match(self::$patterns['invalid_characters'], $tweet)) {
return false; return false;
} }
return true; return true;
} }
/** /**
* Check whether a tweet is valid. * Check whether a tweet is valid.
* *
* @return boolean Whether the tweet is valid. * @return bool Whether the tweet is valid.
*
* @deprecated since version 1.1.0 * @deprecated since version 1.1.0
*/ */
public function validateTweet() public function validateTweet()
@ -199,7 +200,8 @@ class Validator extends Regex
* Check whether a username is valid. * Check whether a username is valid.
* *
* @param string $username The username to validate. * @param string $username The username to validate.
* @return boolean Whether the username is valid. *
* @return bool Whether the username is valid.
*/ */
public function isValidUsername($username = null) public function isValidUsername($username = null)
{ {
@ -211,13 +213,15 @@ class Validator extends Regex
return false; return false;
} }
$extracted = $this->extractor->extractMentionedScreennames($username); $extracted = $this->extractor->extractMentionedScreennames($username);
return count($extracted) === 1 && $extracted[0] === substr($username, 1); return count($extracted) === 1 && $extracted[0] === substr($username, 1);
} }
/** /**
* Check whether a username is valid. * Check whether a username is valid.
* *
* @return boolean Whether the username is valid. * @return bool Whether the username is valid.
*
* @deprecated since version 1.1.0 * @deprecated since version 1.1.0
*/ */
public function validateUsername() public function validateUsername()
@ -229,7 +233,8 @@ class Validator extends Regex
* Check whether a list is valid. * Check whether a list is valid.
* *
* @param string $list The list name to validate. * @param string $list The list name to validate.
* @return boolean Whether the list is valid. *
* @return bool Whether the list is valid.
*/ */
public function isValidList($list = null) public function isValidList($list = null)
{ {
@ -242,13 +247,15 @@ class Validator extends Regex
} }
preg_match(self::$patterns['valid_mentions_or_lists'], $list, $matches); preg_match(self::$patterns['valid_mentions_or_lists'], $list, $matches);
$matches = array_pad($matches, 5, ''); $matches = array_pad($matches, 5, '');
return isset($matches) && $matches[1] === '' && $matches[4] && !empty($matches[4]) && $matches[5] === ''; return isset($matches) && $matches[1] === '' && $matches[4] && !empty($matches[4]) && $matches[5] === '';
} }
/** /**
* Check whether a list is valid. * Check whether a list is valid.
* *
* @return boolean Whether the list is valid. * @return bool Whether the list is valid.
*
* @deprecated since version 1.1.0 * @deprecated since version 1.1.0
*/ */
public function validateList() public function validateList()
@ -260,7 +267,8 @@ class Validator extends Regex
* Check whether a hashtag is valid. * Check whether a hashtag is valid.
* *
* @param string $hashtag The hashtag to validate. * @param string $hashtag The hashtag to validate.
* @return boolean Whether the hashtag is valid. *
* @return bool Whether the hashtag is valid.
*/ */
public function isValidHashtag($hashtag = null) public function isValidHashtag($hashtag = null)
{ {
@ -272,13 +280,15 @@ class Validator extends Regex
return false; return false;
} }
$extracted = $this->extractor->extractHashtags($hashtag); $extracted = $this->extractor->extractHashtags($hashtag);
return count($extracted) === 1 && $extracted[0] === substr($hashtag, 1); return count($extracted) === 1 && $extracted[0] === substr($hashtag, 1);
} }
/** /**
* Check whether a hashtag is valid. * Check whether a hashtag is valid.
* *
* @return boolean Whether the hashtag is valid. * @return bool Whether the hashtag is valid.
*
* @deprecated since version 1.1.0 * @deprecated since version 1.1.0
*/ */
public function validateHashtag() public function validateHashtag()
@ -289,11 +299,11 @@ class Validator extends Regex
/** /**
* Check whether a URL is valid. * Check whether a URL is valid.
* *
* @param string $url The url to validate. * @param string $url The url to validate.
* @param boolean $unicode_domains Consider the domain to be unicode. * @param bool $unicode_domains Consider the domain to be unicode.
* @param boolean $require_protocol Require a protocol for valid domain? * @param bool $require_protocol Require a protocol for valid domain?
* *
* @return boolean Whether the URL is valid. * @return bool Whether the URL is valid.
*/ */
public function isValidURL($url = null, $unicode_domains = true, $require_protocol = true) public function isValidURL($url = null, $unicode_domains = true, $require_protocol = true)
{ {
@ -310,25 +320,27 @@ class Validator extends Regex
return false; return false;
} }
list($scheme, $authority, $path, $query, $fragment) = array_pad($matches, 5, ''); list($scheme, $authority, $path, $query, $fragment) = array_pad($matches, 5, '');
# Check scheme, path, query, fragment: // Check scheme, path, query, fragment:
if (($require_protocol && !( if (($require_protocol && !(
self::isValidMatch($scheme, self::$patterns['validate_url_scheme']) && preg_match('/^https?$/i', $scheme)) self::isValidMatch($scheme, self::$patterns['validate_url_scheme']) && preg_match('/^https?$/i', $scheme))
) || !self::isValidMatch($path, self::$patterns['validate_url_path']) || !self::isValidMatch($query, self::$patterns['validate_url_query'], true) ) || !self::isValidMatch($path, self::$patterns['validate_url_path']) || !self::isValidMatch($query, self::$patterns['validate_url_query'], true)
|| !self::isValidMatch($fragment, self::$patterns['validate_url_fragment'], true)) { || !self::isValidMatch($fragment, self::$patterns['validate_url_fragment'], true)) {
return false; return false;
} }
# Check authority: // Check authority:
$authority_pattern = $unicode_domains ? 'validate_url_unicode_authority' : 'validate_url_authority'; $authority_pattern = $unicode_domains ? 'validate_url_unicode_authority' : 'validate_url_authority';
return self::isValidMatch($authority, self::$patterns[$authority_pattern]); return self::isValidMatch($authority, self::$patterns[$authority_pattern]);
} }
/** /**
* Check whether a URL is valid. * Check whether a URL is valid.
* *
* @param boolean $unicode_domains Consider the domain to be unicode. * @param bool $unicode_domains Consider the domain to be unicode.
* @param boolean $require_protocol Require a protocol for valid domain? * @param bool $require_protocol Require a protocol for valid domain?
*
* @return bool Whether the URL is valid.
* *
* @return boolean Whether the URL is valid.
* @deprecated since version 1.1.0 * @deprecated since version 1.1.0
*/ */
public function validateURL($unicode_domains = true, $require_protocol = true) public function validateURL($unicode_domains = true, $require_protocol = true)
@ -340,7 +352,8 @@ class Validator extends Regex
* Determines the length of a tweet. Takes shortening of URLs into account. * Determines the length of a tweet. Takes shortening of URLs into account.
* *
* @param string $tweet The tweet to validate. * @param string $tweet The tweet to validate.
* @return int the length of a tweet. *
* @return int the length of a tweet.
*/ */
public function getTweetLength($tweet = null) public function getTweetLength($tweet = null)
{ {
@ -353,13 +366,15 @@ class Validator extends Regex
$length += $x['indices'][0] - $x['indices'][1]; $length += $x['indices'][0] - $x['indices'][1];
$length += stripos($x['url'], 'https://') === 0 ? $this->short_url_length_https : $this->short_url_length; $length += stripos($x['url'], 'https://') === 0 ? $this->short_url_length_https : $this->short_url_length;
} }
return $length; return $length;
} }
/** /**
* Determines the length of a tweet. Takes shortening of URLs into account. * Determines the length of a tweet. Takes shortening of URLs into account.
* *
* @return int the length of a tweet. * @return int the length of a tweet.
*
* @deprecated since version 1.1.0 * @deprecated since version 1.1.0
*/ */
public function getLength() public function getLength()
@ -370,17 +385,17 @@ class Validator extends Regex
/** /**
* A helper function to check for a valid match. Used in URL validation. * A helper function to check for a valid match. Used in URL validation.
* *
* @param string $string The subject string to test. * @param string $string The subject string to test.
* @param string $pattern The pattern to match against. * @param string $pattern The pattern to match against.
* @param boolean $optional Whether a match is compulsory or not. * @param bool $optional Whether a match is compulsory or not.
* *
* @return boolean Whether an exact match was found. * @return bool Whether an exact match was found.
*/ */
protected static function isValidMatch($string, $pattern, $optional = false) protected static function isValidMatch($string, $pattern, $optional = false)
{ {
$found = preg_match($pattern, $string, $matches); $found = preg_match($pattern, $string, $matches);
if (!$optional) { if (!$optional) {
return (($string || $string === '') && $found && $matches[0] === $string); return ($string || $string === '') && $found && $matches[0] === $string;
} else { } else {
return !(($string || $string === '') && (!$found || $matches[0] !== $string)); return !(($string || $string === '') && (!$found || $matches[0] !== $string));
} }

View file

@ -2,151 +2,145 @@
namespace App\Util\Media; namespace App\Util\Media;
use Storage;
use App\Media; use App\Media;
use Image as Intervention; use Image as Intervention;
use Storage;
class Image { class Image
{
public $square;
public $landscape;
public $portrait;
public $thumbnail;
public $orientation;
public $square; public function __construct()
public $landscape; {
public $portrait; ini_set('memory_limit', config('pixelfed.memory_limit', '1024M'));
public $thumbnail;
public $orientation;
$this->square = $this->orientations()['square'];
public function __construct() $this->landscape = $this->orientations()['landscape'];
{ $this->portrait = $this->orientations()['portrait'];
ini_set('memory_limit', config('pixelfed.memory_limit', '1024M')); $this->thumbnail = [
'width' => 293,
$this->square = $this->orientations()['square']; 'height' => 293,
$this->landscape = $this->orientations()['landscape'];
$this->portrait = $this->orientations()['portrait'];
$this->thumbnail = [
'width' => 293,
'height' => 293
]; ];
$this->orientation = null; $this->orientation = null;
} }
public function orientations() public function orientations()
{ {
return [ return [
'square' => [ 'square' => [
'width' => 1080, 'width' => 1080,
'height' => 1080 'height' => 1080,
], ],
'landscape' => [ 'landscape' => [
'width' => 1920, 'width' => 1920,
'height' => 1080 'height' => 1080,
], ],
'portrait' => [ 'portrait' => [
'width' => 1080, 'width' => 1080,
'height' => 1350 'height' => 1350,
] ],
]; ];
}
public function getAspectRatio($mediaPath, $thumbnail = false)
{
if(!is_file($mediaPath)) {
throw new \Exception('Invalid Media Path');
}
if($thumbnail) {
return [
'dimensions' => $this->thumbnail,
'orientation' => 'thumbnail'
];
} }
list($width, $height) = getimagesize($mediaPath); public function getAspectRatio($mediaPath, $thumbnail = false)
$aspect = $width / $height;
$orientation = $aspect === 1 ? 'square' :
($aspect > 1 ? 'landscape' : 'portrait');
$this->orientation = $orientation;
return [
'dimensions' => $this->orientations()[$orientation],
'orientation' => $orientation
];
}
public function resizeImage(Media $media)
{
$basePath = storage_path('app/' . $media->media_path);
$this->handleResizeImage($media);
return;
}
public function resizeThumbnail(Media $media)
{
$basePath = storage_path('app/' . $media->media_path);
$this->handleThumbnailImage($media);
return;
}
public function handleResizeImage(Media $media)
{
$this->handleImageTransform($media, false);
}
public function handleThumbnailImage(Media $media)
{
$this->handleImageTransform($media, true);
}
public function handleImageTransform(Media $media, $thumbnail = false)
{
$path = $media->media_path;
$file = storage_path('app/'.$path);
$ratio = $this->getAspectRatio($file, $thumbnail);
$aspect = $ratio['dimensions'];
$orientation = $ratio['orientation'];
if($media->mime === 'image/gif' && !$thumbnail)
{ {
return; if (!is_file($mediaPath)) {
throw new \Exception('Invalid Media Path');
}
if ($thumbnail) {
return [
'dimensions' => $this->thumbnail,
'orientation' => 'thumbnail',
];
}
list($width, $height) = getimagesize($mediaPath);
$aspect = $width / $height;
$orientation = $aspect === 1 ? 'square' :
($aspect > 1 ? 'landscape' : 'portrait');
$this->orientation = $orientation;
return [
'dimensions' => $this->orientations()[$orientation],
'orientation' => $orientation,
];
} }
try { public function resizeImage(Media $media)
$img = Intervention::make($file)->orientate(); {
$img->resize($aspect['width'], $aspect['height'], function ($constraint) { $basePath = storage_path('app/'.$media->media_path);
$constraint->aspectRatio();
});
$converted = $this->setBaseName($path, $thumbnail, $img->extension);
$newPath = storage_path('app/'.$converted['path']);
$quality = config('pixelfed.image_quality');
$img->save($newPath, $quality);
if(!$thumbnail) {
$media->orientation = $orientation;
}
if($thumbnail == true) { $this->handleResizeImage($media);
$media->thumbnail_path = $converted['path'];
$media->thumbnail_url = url(Storage::url($converted['path']));
} else {
$media->media_path = $converted['path'];
$media->mime = $img->mime;
}
$media->save();
} catch (Exception $e) {
} }
}
public function setBaseName($basePath, $thumbnail = false, $extension) public function resizeThumbnail(Media $media)
{ {
$png = false; $basePath = storage_path('app/'.$media->media_path);
$path = explode('.', $basePath);
$name = ($thumbnail == true) ? $path[0] . '_thumb' : $path[0];
$ext = last($path);
$basePath = "{$name}.{$ext}";
return ['path' => $basePath, 'png' => $png]; $this->handleThumbnailImage($media);
} }
} public function handleResizeImage(Media $media)
{
$this->handleImageTransform($media, false);
}
public function handleThumbnailImage(Media $media)
{
$this->handleImageTransform($media, true);
}
public function handleImageTransform(Media $media, $thumbnail = false)
{
$path = $media->media_path;
$file = storage_path('app/'.$path);
$ratio = $this->getAspectRatio($file, $thumbnail);
$aspect = $ratio['dimensions'];
$orientation = $ratio['orientation'];
if ($media->mime === 'image/gif' && !$thumbnail) {
return;
}
try {
$img = Intervention::make($file)->orientate();
$img->resize($aspect['width'], $aspect['height'], function ($constraint) {
$constraint->aspectRatio();
});
$converted = $this->setBaseName($path, $thumbnail, $img->extension);
$newPath = storage_path('app/'.$converted['path']);
$quality = config('pixelfed.image_quality');
$img->save($newPath, $quality);
if (!$thumbnail) {
$media->orientation = $orientation;
}
if ($thumbnail == true) {
$media->thumbnail_path = $converted['path'];
$media->thumbnail_url = url(Storage::url($converted['path']));
} else {
$media->media_path = $converted['path'];
$media->mime = $img->mime;
}
$media->save();
} catch (Exception $e) {
}
}
public function setBaseName($basePath, $thumbnail, $extension)
{
$png = false;
$path = explode('.', $basePath);
$name = ($thumbnail == true) ? $path[0].'_thumb' : $path[0];
$ext = last($path);
$basePath = "{$name}.{$ext}";
return ['path' => $basePath, 'png' => $png];
}
}

Some files were not shown because too many files have changed in this diff Show more