Merge pull request #417 from pixelfed/frontend-ui-refactor

Frontend ui refactor
This commit is contained in:
daniel 2018-08-27 22:06:36 -06:00 committed by GitHub
commit 3b4705b4e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
262 changed files with 3427 additions and 3237 deletions

54
.circleci/config.yml Normal file
View file

@ -0,0 +1,54 @@
# PHP CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-php/ for more details
#
version: 2
jobs:
build:
docker:
# Specify the version you desire here
- image: circleci/php:7.1-jessie-node-browsers
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
# Using the RAM variation mitigates I/O contention
# for database intensive operations.
# - image: circleci/mysql:5.7-ram
#
# - image: redis:2.8.19
steps:
- checkout
- run: sudo apt update && sudo apt install zlib1g-dev libsqlite3-dev
- run: sudo docker-php-ext-install bcmath pcntl zip
# Download and cache dependencies
# composer cache
- restore_cache:
keys:
# "composer.lock" can be used if it is committed to the repo
- v1-dependencies-{{ checksum "composer.json" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run: composer install -n --prefer-dist
- save_cache:
key: composer-v1-{{ checksum "composer.lock" }}
paths:
- vendor
- run: cp .env.example .env
- run: php artisan storage:link
- run: php artisan key:generate
- run: php artisan config:clear
# run tests with phpunit or codecept
- run: ./vendor/bin/phpunit
- store_test_results:
path: tests/_output
- store_artifacts:
path: tests/_output

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,11 +6,11 @@ use Illuminate\Database\Eloquent\Model;
class Hashtag extends Model
{
public $fillable = ['name','slug'];
public $fillable = ['name', 'slug'];
public function posts()
{
return $this->hasManyThrough(
return $this->hasManyThrough(
Status::class,
StatusHashtag::class,
'hashtag_id',
@ -22,7 +22,6 @@ class Hashtag extends Model
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;
use Illuminate\Http\Request;
use Carbon\Carbon;
use App\EmailVerification;
use App\Mail\ConfirmEmail;
use Auth, DB, Cache, Mail, Redis;
use App\{
EmailVerification,
Notification,
Profile,
User,
UserFilter
};
use App\Notification;
use App\Profile;
use App\User;
use App\UserFilter;
use Auth;
use Cache;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Mail;
use Redis;
class AccountController extends Controller
{
protected $filters = [
'user.mute',
'user.block'
'user.block',
];
public function __construct()
{
$this->middleware('auth');
$this->middleware('auth');
}
public function notifications(Request $request)
{
$this->validate($request, [
$this->validate($request, [
'page' => 'nullable|min:1|max:3',
'a' => 'nullable|alpha_dash',
]);
$profile = Auth::user()->profile;
$action = $request->input('a');
$timeago = Carbon::now()->subMonths(6);
if($action && in_array($action, ['comment', 'follow', 'mention'])) {
$notifications = Notification::whereProfileId($profile->id)
$profile = Auth::user()->profile;
$action = $request->input('a');
$timeago = Carbon::now()->subMonths(6);
if ($action && in_array($action, ['comment', 'follow', 'mention'])) {
$notifications = Notification::whereProfileId($profile->id)
->whereAction($action)
->whereDate('created_at', '>', $timeago)
->orderBy('id','desc')
->orderBy('id', 'desc')
->simplePaginate(30);
} else {
$notifications = Notification::whereProfileId($profile->id)
} else {
$notifications = Notification::whereProfileId($profile->id)
->whereDate('created_at', '>', $timeago)
->orderBy('id','desc')
->orderBy('id', 'desc')
->simplePaginate(30);
}
}
return view('account.activity', compact('profile', 'notifications'));
return view('account.activity', compact('profile', 'notifications'));
}
public function followingActivity(Request $request)
{
$this->validate($request, [
$this->validate($request, [
'page' => 'nullable|min:1|max:3',
'a' => 'nullable|alpha_dash',
]);
$profile = Auth::user()->profile;
$action = $request->input('a');
$timeago = Carbon::now()->subMonths(1);
$following = $profile->following->pluck('id');
$notifications = Notification::whereIn('actor_id', $following)
$profile = Auth::user()->profile;
$action = $request->input('a');
$timeago = Carbon::now()->subMonths(1);
$following = $profile->following->pluck('id');
$notifications = Notification::whereIn('actor_id', $following)
->where('profile_id', '!=', $profile->id)
->whereDate('created_at', '>', $timeago)
->orderBy('notifications.id','desc')
->orderBy('notifications.id', 'desc')
->simplePaginate(30);
return view('account.following', compact('profile', 'notifications'));
return view('account.following', compact('profile', 'notifications'));
}
public function verifyEmail(Request $request)
{
return view('account.verify_email');
return view('account.verify_email');
}
public function sendVerifyEmail(Request $request)
@ -82,19 +83,18 @@ class AccountController extends Controller
->where('created_at', '>', $timeLimit)->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.');
} elseif ($recentAttempt == 0 && $exists !== 0) {
// Delete old verification and send new one.
EmailVerification::whereUserId(Auth::id())->delete();
}
$user = User::whereNull('email_verified_at')->find(Auth::id());
$utoken = hash('sha512', $user->id);
$rtoken = str_random(40);
$verify = new EmailVerification;
$verify = new EmailVerification();
$verify->user_id = $user->id;
$verify->email = $user->email;
$verify->user_token = $utoken;
@ -112,55 +112,56 @@ class AccountController extends Controller
->where('random_token', $randomToken)
->firstOrFail();
if(Auth::id() === $verify->user_id) {
$user = User::find(Auth::id());
$user->email_verified_at = Carbon::now();
$user->save();
return redirect('/');
if (Auth::id() === $verify->user_id) {
$user = User::find(Auth::id());
$user->email_verified_at = Carbon::now();
$user->save();
return redirect('/');
}
}
public function fetchNotifications($id)
{
$key = config('cache.prefix') . ":user.{$id}.notifications";
$redis = Redis::connection();
$notifications = $redis->lrange($key, 0, 30);
if(empty($notifications)) {
$notifications = Notification::whereProfileId($id)
->orderBy('id','desc')->take(30)->get();
} else {
$notifications = $this->hydrateNotifications($notifications);
}
$key = config('cache.prefix').":user.{$id}.notifications";
$redis = Redis::connection();
$notifications = $redis->lrange($key, 0, 30);
if (empty($notifications)) {
$notifications = Notification::whereProfileId($id)
->orderBy('id', 'desc')->take(30)->get();
} else {
$notifications = $this->hydrateNotifications($notifications);
}
return $notifications;
return $notifications;
}
public function hydrateNotifications($keys)
{
$prefix = 'notification.';
$notifications = collect([]);
foreach($keys as $key) {
$notifications->push(Cache::get("{$prefix}{$key}"));
}
return $notifications;
$prefix = 'notification.';
$notifications = collect([]);
foreach ($keys as $key) {
$notifications->push(Cache::get("{$prefix}{$key}"));
}
return $notifications;
}
public function messages()
{
return view('account.messages');
return view('account.messages');
}
public function showMessage(Request $request, $id)
{
return view('account.message');
return view('account.message');
}
public function mute(Request $request)
{
$this->validate($request, [
'type' => 'required|string',
'item' => 'required|integer|min:1'
'item' => 'required|integer|min:1',
]);
$user = Auth::user()->profile;
@ -168,15 +169,15 @@ class AccountController extends Controller
$item = $request->input('item');
$action = "{$type}.mute";
if(!in_array($action, $this->filters)) {
return abort(406);
if (!in_array($action, $this->filters)) {
return abort(406);
}
$filterable = [];
switch ($type) {
case 'user':
$profile = Profile::findOrFail($item);
if($profile->id == $user->id) {
return abort(403);
if ($profile->id == $user->id) {
return abort(403);
}
$class = get_class($profile);
$filterable['id'] = $profile->id;
@ -184,34 +185,33 @@ class AccountController extends Controller
break;
default:
# code...
// code...
break;
}
$filter = UserFilter::firstOrCreate([
'user_id' => $user->id,
'filterable_id' => $filterable['id'],
'user_id' => $user->id,
'filterable_id' => $filterable['id'],
'filterable_type' => $filterable['type'],
'filter_type' => 'mute'
'filter_type' => 'mute',
]);
return redirect()->back();
}
public function block(Request $request)
{
$this->validate($request, [
'type' => 'required|string',
'item' => 'required|integer|min:1'
'item' => 'required|integer|min:1',
]);
$user = Auth::user()->profile;
$type = $request->input('type');
$item = $request->input('item');
$action = "{$type}.block";
if(!in_array($action, $this->filters)) {
return abort(406);
if (!in_array($action, $this->filters)) {
return abort(406);
}
$filterable = [];
switch ($type) {
@ -223,19 +223,17 @@ class AccountController extends Controller
break;
default:
# code...
// code...
break;
}
$filter = UserFilter::firstOrCreate([
'user_id' => $user->id,
'filterable_id' => $filterable['id'],
'user_id' => $user->id,
'filterable_id' => $filterable['id'],
'filterable_type' => $filterable['type'],
'filter_type' => 'block'
'filter_type' => 'block',
]);
return redirect()->back();
}
}

View file

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

View file

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

View file

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

View file

@ -2,14 +2,14 @@
namespace App\Http\Controllers;
use Auth, Cache;
use App\{Like, Status};
use Illuminate\Http\Request;
use App\Http\Controllers\Api\BaseApiController;
use App\Like;
use Auth;
use Cache;
use Illuminate\Http\Request;
class ApiController extends BaseApiController
{
public function hydrateLikes(Request $request)
{
$this->validate($request, [
@ -18,7 +18,7 @@ class ApiController extends BaseApiController
]);
$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)
->orderBy('id', 'desc')
->take(1000)
@ -30,6 +30,5 @@ class ApiController extends BaseApiController
public function loadMoreComments(Request $request)
{
return;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,10 +2,10 @@
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\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{

View file

@ -2,57 +2,60 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\{Hashtag, Follower, Profile, Status, StatusHashtag};
use App\Follower;
use App\Hashtag;
use App\Profile;
use App\Status;
use Auth;
use Illuminate\Http\Request;
class DiscoverController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('auth');
}
public function home()
{
$pid = Auth::user()->profile->id;
$pid = Auth::user()->profile->id;
$following = Follower::whereProfileId($pid)
$following = Follower::whereProfileId($pid)
->pluck('following_id');
$people = Profile::inRandomOrder()
$people = Profile::inRandomOrder()
->where('id', '!=', $pid)
->whereNotIn('id', $following)
->take(3)
->get();
$posts = Status::whereHas('media')
$posts = Status::whereHas('media')
->where('profile_id', '!=', $pid)
->whereNotIn('profile_id', $following)
->orderBy('created_at', 'desc')
->simplePaginate(21);
return view('discover.home', compact('people', 'posts'));
return view('discover.home', compact('people', 'posts'));
}
public function showTags(Request $request, $hashtag)
{
$this->validate($request, [
'page' => 'nullable|integer|min:1|max:10'
$this->validate($request, [
'page' => 'nullable|integer|min:1|max:10',
]);
$tag = Hashtag::with('posts')
$tag = Hashtag::with('posts')
->withCount('posts')
->whereSlug($hashtag)
->firstOrFail();
$posts = $tag->posts()
$posts = $tag->posts()
->whereIsNsfw(false)
->whereVisibility('public')
->has('media')
->orderBy('id','desc')
->orderBy('id', 'desc')
->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;
use Auth, Cache;
use App\Jobs\InboxPipeline\InboxWorker;
use App\Jobs\RemoteFollowPipeline\RemoteFollowPipeline;
use App\Profile;
use Carbon\Carbon;
use League\Fractal;
use Illuminate\Http\Request;
use App\Transformer\ActivityPub\ProfileOutbox;
use App\Util\Lexer\Nickname;
use App\Util\Webfinger\Webfinger;
use App\Transformer\ActivityPub\{
ProfileOutbox,
ProfileTransformer
};
use App\Jobs\RemoteFollowPipeline\RemoteFollowPipeline;
use App\Jobs\InboxPipeline\InboxWorker;
use Auth;
use Cache;
use Carbon\Carbon;
use Illuminate\Http\Request;
use League\Fractal;
class FederationController extends Controller
{
public function authCheck()
{
if(!Auth::check()) {
return abort(403);
}
if (!Auth::check()) {
return abort(403);
}
}
public function authorizeFollow(Request $request)
{
$this->authCheck();
$this->validate($request, [
'acct' => 'required|string|min:3|max:255'
$this->authCheck();
$this->validate($request, [
'acct' => 'required|string|min:3|max:255',
]);
$acct = $request->input('acct');
$nickname = Nickname::normalizeProfileUrl($acct);
return view('federation.authorizefollow', compact('acct', 'nickname'));
$acct = $request->input('acct');
$nickname = Nickname::normalizeProfileUrl($acct);
return view('federation.authorizefollow', compact('acct', 'nickname'));
}
public function remoteFollow()
{
$this->authCheck();
return view('federation.remotefollow');
$this->authCheck();
return view('federation.remotefollow');
}
public function remoteFollowStore(Request $request)
{
$this->authCheck();
$this->validate($request, [
'url' => 'required|string'
$this->authCheck();
$this->validate($request, [
'url' => 'required|string',
]);
if(config('pixelfed.remote_follow_enabled') !== true) {
abort(403);
}
if (config('pixelfed.remote_follow_enabled') !== true) {
abort(403);
}
$follower = Auth::user()->profile;
$url = $request->input('url');
$follower = Auth::user()->profile;
$url = $request->input('url');
RemoteFollowPipeline::dispatch($follower, $url);
RemoteFollowPipeline::dispatch($follower, $url);
return redirect()->back();
return redirect()->back();
}
public function nodeinfoWellKnown()
{
$res = [
$res = [
'links' => [
[
'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()
{
$res = Cache::remember('api:nodeinfo', 60, function() {
return [
$res = Cache::remember('api:nodeinfo', 60, function () {
return [
'metadata' => [
'nodeName' => config('app.name'),
'software' => [
'homepage' => 'https://pixelfed.org',
'github' => 'https://github.com/pixelfed',
'follow' => 'https://mastodon.social/@pixelfed'
'github' => 'https://github.com/pixelfed',
'follow' => 'https://mastodon.social/@pixelfed',
],
],
'openRegistrations' => config('pixelfed.open_registration'),
'protocols' => [
'activitypub'
'protocols' => [
'activitypub',
],
'services' => [
'inbound' => [],
'outbound' => []
'inbound' => [],
'outbound' => [],
],
'software' => [
'name' => 'pixelfed',
'version' => config('pixelfed.version')
'name' => 'pixelfed',
'version' => config('pixelfed.version'),
],
'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(),
'users' => [
'total' => \App\User::count(),
'users' => [
'total' => \App\User::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)
{
$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'));
$hash = hash('sha256', $request->input('resource'));
$webfinger = Cache::remember('api:webfinger:'.$hash, 1440, function() use($request) {
$resource = $request->input('resource');
$parsed = Nickname::normalizeProfileUrl($resource);
$username = $parsed['username'];
$user = Profile::whereUsername($username)->firstOrFail();
return (new Webfinger($user))->generate();
});
return response()->json($webfinger, 200, [], JSON_PRETTY_PRINT);
$webfinger = Cache::remember('api:webfinger:'.$hash, 1440, function () use ($request) {
$resource = $request->input('resource');
$parsed = Nickname::normalizeProfileUrl($resource);
$username = $parsed['username'];
$user = Profile::whereUsername($username)->firstOrFail();
return (new Webfinger($user))->generate();
});
return response()->json($webfinger, 200, [], JSON_PRETTY_PRINT);
}
public function userOutbox(Request $request, $username)
{
if(config('pixelfed.activitypub_enabled') == false) {
abort(403);
}
if (config('pixelfed.activitypub_enabled') == false) {
abort(403);
}
$user = Profile::whereNull('remote_url')->whereUsername($username)->firstOrFail();
$timeline = $user->statuses()->orderBy('created_at','desc')->paginate(10);
$fractal = new Fractal\Manager();
$resource = new Fractal\Resource\Item($user, new ProfileOutbox);
$res = $fractal->createData($resource)->toArray();
return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
$user = Profile::whereNull('remote_url')->whereUsername($username)->firstOrFail();
$timeline = $user->statuses()->orderBy('created_at', 'desc')->paginate(10);
$fractal = new Fractal\Manager();
$resource = new Fractal\Resource\Item($user, new ProfileOutbox());
$res = $fractal->createData($resource)->toArray();
return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
}
public function userInbox(Request $request, $username)
{
if(config('pixelfed.activitypub_enabled') == false) {
abort(403);
}
$mimes = [
if (config('pixelfed.activitypub_enabled') == false) {
abort(403);
}
$mimes = [
'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)) {
abort(500, 'Invalid request');
}
$profile = Profile::whereUsername($username)->firstOrFail();
InboxWorker::dispatch($request, $profile, $request->all());
if (!in_array($request->header('Content-Type'), $mimes)) {
abort(500, 'Invalid request');
}
$profile = Profile::whereUsername($username)->firstOrFail();
InboxWorker::dispatch($request, $profile, $request->all());
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,9 +2,12 @@
namespace App\Http\Controllers;
use App\Profile;
use App\Report;
use App\Status;
use App\User;
use Auth;
use Illuminate\Http\Request;
use App\{Avatar, Profile, Report, Status, User};
class ReportController extends Controller
{
@ -12,95 +15,96 @@ class ReportController extends Controller
public function __construct()
{
$this->middleware('auth');
$this->middleware('auth');
}
public function showForm(Request $request)
{
$this->validate($request, [
$this->validate($request, [
'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)
{
return view('report.not-interested');
return view('report.not-interested');
}
public function spamForm(Request $request)
{
return view('report.spam');
return view('report.spam');
}
public function spamCommentForm(Request $request)
{
return view('report.spam.comment');
return view('report.spam.comment');
}
public function spamPostForm(Request $request)
{
return view('report.spam.post');
return view('report.spam.post');
}
public function spamProfileForm(Request $request)
{
return view('report.spam.profile');
return view('report.spam.profile');
}
public function sensitiveCommentForm(Request $request)
{
return view('report.sensitive.comment');
return view('report.sensitive.comment');
}
public function sensitivePostForm(Request $request)
{
return view('report.sensitive.post');
return view('report.sensitive.post');
}
public function sensitiveProfileForm(Request $request)
{
return view('report.sensitive.profile');
return view('report.sensitive.profile');
}
public function abusiveCommentForm(Request $request)
{
return view('report.abusive.comment');
return view('report.abusive.comment');
}
public function abusivePostForm(Request $request)
{
return view('report.abusive.post');
return view('report.abusive.post');
}
public function abusiveProfileForm(Request $request)
{
return view('report.abusive.profile');
return view('report.abusive.profile');
}
public function formStore(Request $request)
{
$this->validate($request, [
$this->validate($request, [
'report' => 'required|alpha_dash',
'type' => 'required|alpha_dash',
'id' => 'required|integer|min:1',
'msg' => 'nullable|string|max:150'
'msg' => 'nullable|string|max:150',
]);
$profile = Auth::user()->profile;
$reportType = $request->input('report');
$object_id = $request->input('id');
$object_type = $request->input('type');
$msg = $request->input('msg');
$object = null;
$types = ['spam', 'sensitive', 'abusive'];
$profile = Auth::user()->profile;
$reportType = $request->input('report');
$object_id = $request->input('id');
$object_type = $request->input('type');
$msg = $request->input('msg');
$object = null;
$types = ['spam', 'sensitive', 'abusive'];
if(!in_array($reportType, $types)) {
return redirect('/timeline')->with('error', 'Invalid report type');
}
if (!in_array($reportType, $types)) {
return redirect('/timeline')->with('error', 'Invalid report type');
}
switch ($object_type) {
switch ($object_type) {
case 'post':
$object = Status::findOrFail($object_id);
$object_type = 'App\Status';
@ -115,25 +119,24 @@ class ReportController extends Controller
break;
}
if($exists !== 0) {
return redirect('/timeline')->with('error', 'You have already reported this!');
}
if ($exists !== 0) {
return redirect('/timeline')->with('error', 'You have already reported this!');
}
if($object->profile_id == $profile->id) {
return redirect('/timeline')->with('error', 'You cannot report your own content!');
}
if ($object->profile_id == $profile->id) {
return redirect('/timeline')->with('error', 'You cannot report your own content!');
}
$report = new Report;
$report->profile_id = $profile->id;
$report->user_id = Auth::id();
$report->object_id = $object->id;
$report->object_type = $object_type;
$report->reported_profile_id = $object->profile_id;
$report->type = $request->input('report');
$report->message = $request->input('msg');
$report->save();
$report = new Report();
$report->profile_id = $profile->id;
$report->user_id = Auth::id();
$report->object_id = $object->id;
$report->object_type = $object_type;
$report->reported_profile_id = $object->profile_id;
$report->type = $request->input('report');
$report->message = $request->input('msg');
$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;
use App\{Hashtag, Profile};
use App\Hashtag;
use App\Profile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
@ -10,33 +11,34 @@ class SearchController extends Controller
{
public function searchAPI(Request $request, $tag)
{
$res = Cache::remember('api:search:tag:' . $tag, 1440, function() use($tag) {
$res = Hashtag::where('slug', 'like', '%'.$tag.'%')->get();
$tags = $res->map(function($item, $key) {
return [
'count' => $item->posts()->count(),
'url' => $item->url(),
'type' => 'hashtag',
'value' => $item->name,
$res = Cache::remember('api:search:tag:'.$tag, 1440, function () use ($tag) {
$res = Hashtag::where('slug', 'like', '%'.$tag.'%')->get();
$tags = $res->map(function ($item, $key) {
return [
'count' => $item->posts()->count(),
'url' => $item->url(),
'type' => 'hashtag',
'value' => $item->name,
'tokens' => explode('-', $item->name),
'name' => null
'name' => null,
];
});
$res = Profile::where('username', 'like', '%'.$tag.'%')->get();
$profiles = $res->map(function($item, $key) {
return [
'count' => 0,
'url' => $item->url(),
'type' => 'profile',
'value' => $item->username,
});
$res = Profile::where('username', 'like', '%'.$tag.'%')->get();
$profiles = $res->map(function ($item, $key) {
return [
'count' => 0,
'url' => $item->url(),
'type' => 'profile',
'value' => $item->username,
'tokens' => [$item->username],
'name' => $item->name
'name' => $item->name,
];
});
$tags = $tags->push($profiles[0]);
return $tags;
});
});
$tags = $tags->push($profiles[0]);
return response()->json($res);
return $tags;
});
return response()->json($res);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,20 +2,22 @@
namespace App\Http\Middleware;
use Auth, Closure;
use Auth;
use Closure;
class Admin
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @return mixed
*/
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'));
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,14 +2,14 @@
namespace App\Jobs\StatusPipeline;
use Cache, Redis;
use App\{Media, Status};
use App\Jobs\ImageOptimizePipeline\ImageOptimize;
use App\Status;
use Cache;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Redis;
class NewStatusPipeline implements ShouldQueue
{
@ -39,9 +39,9 @@ class NewStatusPipeline implements ShouldQueue
StatusEntityLexer::dispatch($status);
//StatusActivityPubDeliver::dispatch($status);
Cache::forever('post.' . $status->id, $status);
Cache::forever('post.'.$status->id, $status);
$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;
use App\{Media, Status};
use App\Status;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class StatusActivityPubDeliver implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $status;
/**
* Create a new job instance.
*

View file

@ -2,12 +2,14 @@
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\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class StatusDelete implements ShouldQueue
{
@ -38,28 +40,27 @@ class StatusDelete implements ShouldQueue
public function unlinkRemoveMedia($status)
{
if($status->media()->count() == 0) {
if ($status->media()->count() == 0) {
return;
}
foreach($status->media as $media) {
foreach ($status->media as $media) {
$thumbnail = storage_path("app/{$media->thumbnail_path}");
$photo = storage_path("app/{$media->media_path}");
try {
if(is_file($thumbnail)) {
if (is_file($thumbnail)) {
unlink($thumbnail);
}
if(is_file($photo)) {
if (is_file($photo)) {
unlink($photo);
}
$media->delete();
} catch (Exception $e) {
}
}
$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->save();
Notification::whereItemType('App\Status')

View file

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

View file

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

View file

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

View file

@ -2,9 +2,9 @@
namespace App;
use Storage;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Storage;
class Media extends Model
{
@ -19,15 +19,17 @@ class Media extends Model
public function url()
{
$path = $this->media_path;
$url = Storage::url($path);
return url($url);
$path = $this->media_path;
$url = Storage::url($path);
return url($url);
}
public function thumbnailUrl()
{
$path = $this->thumbnail_path;
$url = Storage::url($path);
return url($url);
$path = $this->thumbnail_path;
$url = Storage::url($path);
return url($url);
}
}

View file

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

View file

@ -18,22 +18,21 @@ class Notification extends Model
public function actor()
{
return $this->belongsTo(Profile::class, 'actor_id', 'id');
return $this->belongsTo(Profile::class, 'actor_id', 'id');
}
public function profile()
{
return $this->belongsTo(Profile::class, 'profile_id', 'id');
return $this->belongsTo(Profile::class, 'profile_id', 'id');
}
public function item()
{
return $this->morphTo();
return $this->morphTo();
}
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;
use App\{Profile, User, UserSetting};
use App\Jobs\AvatarPipeline\CreateAvatar;
use App\Profile;
use App\User;
use App\UserSetting;
class UserObserver
{
/**
* Listen to the User created event.
*
* @param \App\User $user
* @param \App\User $user
*
* @return void
*/
public function saved(User $user)
{
if(empty($user->profile)) {
$profile = new Profile;
if (empty($user->profile)) {
$profile = new Profile();
$profile->user_id = $user->id;
$profile->username = $user->username;
$profile->name = $user->name;
$pkiConfig = [
"digest_alg" => "sha512",
"private_key_bits" => 2048,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
'digest_alg' => 'sha512',
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
];
$pki = openssl_pkey_new($pkiConfig);
openssl_pkey_export($pki, $pki_private);
@ -37,11 +40,10 @@ class UserObserver
CreateAvatar::dispatch($profile);
}
if(empty($user->settings)) {
$settings = new UserSetting;
if (empty($user->settings)) {
$settings = new UserSetting();
$settings->user_id = $user->id;
$settings->save();
}
}
}

View file

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

View file

@ -2,9 +2,10 @@
namespace App\Providers;
use App\User;
use Auth, Horizon;
use App\Observers\UserObserver;
use App\User;
use Auth;
use Horizon;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;
@ -26,34 +27,39 @@ class AppServiceProvider extends ServiceProvider
return Auth::check() && $request->user()->is_admin;
});
Blade::directive('prettyNumber', function($expression) {
Blade::directive('prettyNumber', function ($expression) {
$num = $expression;
$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 "<?php echo '$num'; ?>";
$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;
return "<?php echo '$num'; ?>";
}
}
return "<?php echo $num; ?>";
});
Blade::directive('prettySize', function($expression) {
Blade::directive('prettySize', function ($expression) {
$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) {}
['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 "<?php echo '$res'; ?>";
});
Blade::directive('maxFileSize', function() {
$value = config('pixelfed.max_photo_size');
return \App\Util\Lexer\PrettyNumber::size($value, true);
Blade::directive('maxFileSize', function () {
$value = config('pixelfed.max_photo_size');
return \App\Util\Lexer\PrettyNumber::size($value, true);
});
}

View file

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

View file

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

View file

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

View file

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

View file

@ -10,18 +10,18 @@ class Report extends Model
public function url()
{
return url('/i/admin/reports/show/' . $this->id);
return url('/i/admin/reports/show/'.$this->id);
}
public function reporter()
{
return $this->belongsTo(Profile::class, 'profile_id');
return $this->belongsTo(Profile::class, 'profile_id');
}
public function reported()
{
$class = $this->object_type;
switch ($class) {
$class = $this->object_type;
switch ($class) {
case 'App\Status':
$column = 'id';
break;
@ -30,11 +30,12 @@ class Report extends Model
$column = 'id';
break;
}
return (new $class())->where($column, $this->object_id)->firstOrFail();
return (new $class())->where($column, $this->object_id)->firstOrFail();
}
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;
use Auth, Storage;
use Auth;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Storage;
class Status extends Model
{
@ -19,122 +20,130 @@ class Status extends Model
public function profile()
{
return $this->belongsTo(Profile::class);
return $this->belongsTo(Profile::class);
}
public function media()
{
return $this->hasMany(Media::class);
return $this->hasMany(Media::class);
}
public function firstMedia()
{
return $this->hasMany(Media::class)->orderBy('order', 'asc')->first();
return $this->hasMany(Media::class)->orderBy('order', 'asc')->first();
}
public function viewType()
{
$media = $this->firstMedia();
$type = explode('/', $media->mime);
return $type[0];
$media = $this->firstMedia();
$type = explode('/', $media->mime);
return $type[0];
}
public function thumb($showNsfw = false)
{
$type = $this->viewType();
$is_nsfw = !$showNsfw ? $this->is_nsfw : false;
if($this->media->count() == 0 || $is_nsfw || $type != 'image') {
return "data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==";
}
return url(Storage::url($this->firstMedia()->thumbnail_path));
$type = $this->viewType();
$is_nsfw = !$showNsfw ? $this->is_nsfw : false;
if ($this->media->count() == 0 || $is_nsfw || $type != 'image') {
return 'data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==';
}
return url(Storage::url($this->firstMedia()->thumbnail_path));
}
public function url()
{
$id = $this->id;
$username = $this->profile->username;
$path = config('app.url') . "/p/{$username}/{$id}";
if(!is_null($this->in_reply_to_id)) {
$pid = $this->in_reply_to_id;
$path = config('app.url') . "/p/{$username}/{$pid}/c/{$id}";
}
return url($path);
$id = $this->id;
$username = $this->profile->username;
$path = config('app.url')."/p/{$username}/{$id}";
if (!is_null($this->in_reply_to_id)) {
$pid = $this->in_reply_to_id;
$path = config('app.url')."/p/{$username}/{$pid}/c/{$id}";
}
return url($path);
}
public function permalink($suffix = '/activity')
{
$id = $this->id;
$username = $this->profile->username;
$path = config('app.url') . "/p/{$username}/{$id}{$suffix}";
return url($path);
$id = $this->id;
$username = $this->profile->username;
$path = config('app.url')."/p/{$username}/{$id}{$suffix}";
return url($path);
}
public function editUrl()
{
return $this->url() . '/edit';
return $this->url().'/edit';
}
public function mediaUrl()
{
$media = $this->firstMedia();
$path = $media->media_path;
$hash = is_null($media->processed_at) ? md5('unprocessed') : md5($media->created_at);
$url = Storage::url($path) . "?v={$hash}";
return url($url);
$media = $this->firstMedia();
$path = $media->media_path;
$hash = is_null($media->processed_at) ? md5('unprocessed') : md5($media->created_at);
$url = Storage::url($path)."?v={$hash}";
return url($url);
}
public function likes()
{
return $this->hasMany(Like::class);
return $this->hasMany(Like::class);
}
public function liked() : bool
{
$profile = Auth::user()->profile;
return Like::whereProfileId($profile->id)->whereStatusId($this->id)->count();
$profile = Auth::user()->profile;
return Like::whereProfileId($profile->id)->whereStatusId($this->id)->count();
}
public function comments()
{
return $this->hasMany(Status::class, 'in_reply_to_id');
return $this->hasMany(self::class, 'in_reply_to_id');
}
public function bookmarked()
{
if(!Auth::check()) {
return 0;
}
$profile = Auth::user()->profile;
return Bookmark::whereProfileId($profile->id)->whereStatusId($this->id)->count();
if (!Auth::check()) {
return 0;
}
$profile = Auth::user()->profile;
return Bookmark::whereProfileId($profile->id)->whereStatusId($this->id)->count();
}
public function shares()
{
return $this->hasMany(Status::class, 'reblog_of_id');
return $this->hasMany(self::class, 'reblog_of_id');
}
public function shared() : bool
{
$profile = Auth::user()->profile;
return Status::whereProfileId($profile->id)->whereReblogOfId($this->id)->count();
$profile = Auth::user()->profile;
return self::whereProfileId($profile->id)->whereReblogOfId($this->id)->count();
}
public function parent()
{
$parent = $this->in_reply_to_id ?? $this->reblog_of_id;
if(!empty($parent)) {
return Status::findOrFail($parent);
}
$parent = $this->in_reply_to_id ?? $this->reblog_of_id;
if (!empty($parent)) {
return self::findOrFail($parent);
}
}
public function conversation()
{
return $this->hasOne(Conversation::class);
return $this->hasOne(Conversation::class);
}
public function hashtags()
{
return $this->hasManyThrough(
return $this->hasManyThrough(
Hashtag::class,
StatusHashtag::class,
'status_id',
@ -146,7 +155,7 @@ class Status extends Model
public function mentions()
{
return $this->hasManyThrough(
return $this->hasManyThrough(
Profile::class,
Mention::class,
'status_id',
@ -158,45 +167,48 @@ class Status extends Model
public function reportUrl()
{
return route('report.form') . "?type=post&id={$this->id}";
return route('report.form')."?type=post&id={$this->id}";
}
public function toActivityStream()
{
$media = $this->media;
$mediaCollection = [];
foreach($media as $image) {
$mediaCollection[] = [
"type" => "Link",
"href" => $image->url(),
"mediaType" => $image->mime
$media = $this->media;
$mediaCollection = [];
foreach ($media as $image) {
$mediaCollection[] = [
'type' => 'Link',
'href' => $image->url(),
'mediaType' => $image->mime,
];
}
$obj = [
"@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Image",
"name" => null,
"url" => $mediaCollection
}
$obj = [
'@context' => 'https://www.w3.org/ns/activitystreams',
'type' => 'Image',
'name' => null,
'url' => $mediaCollection,
];
return $obj;
return $obj;
}
public function replyToText()
{
$actorName = $this->profile->username;
return "{$actorName} " . __('notification.commented');
$actorName = $this->profile->username;
return "{$actorName} ".__('notification.commented');
}
public function replyToHtml()
{
$actorName = $this->profile->username;
$actorUrl = $this->profile->url();
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> " .
$actorName = $this->profile->username;
$actorUrl = $this->profile->url();
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> ".
__('notification.commented');
}
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
{
public function transform(Profile $profile)
{
$count = $profile->statuses()->count();
$statuses = $profile->statuses()->has('media')->orderBy('id','desc')->take(20)->get()->map(function($i, $k) {
$item = [
public function transform(Profile $profile)
{
$count = $profile->statuses()->count();
$statuses = $profile->statuses()->has('media')->orderBy('id', 'desc')->take(20)->get()->map(function ($i, $k) {
$item = [
'id' => $i->permalink(),
// TODO: handle other types
'type' => 'Create',
'actor' => $i->profile->url(),
'type' => 'Create',
'actor' => $i->profile->url(),
'published' => $i->created_at->toISO8601String(),
'to' => [
'https://www.w3.org/ns/activitystreams#Public'
'to' => [
'https://www.w3.org/ns/activitystreams#Public',
],
'cc' => [
$i->profile->permalink('/followers'),
@ -31,48 +30,48 @@ class ProfileOutbox extends Fractal\TransformerAbstract
'type' => 'Note',
// XXX: CW Title
'summary' => null,
'content' => $i->rendered ?? $i->caption,
'summary' => null,
'content' => $i->rendered ?? $i->caption,
'inReplyTo' => null,
// TODO: fix date format
'published' => $i->created_at->toAtomString(),
'url' => $i->url(),
'published' => $i->created_at->toAtomString(),
'url' => $i->url(),
'attributedTo' => $i->profile->permalink(),
'to' => [
'to' => [
// TODO: handle proper scope
'https://www.w3.org/ns/activitystreams#Public'
'https://www.w3.org/ns/activitystreams#Public',
],
'cc' => [
// TODO: add cc's
//"{$notice->getProfile()->getUrl()}/subscribers",
],
'sensitive' => (bool) $i->is_nsfw,
'atomUri' => $i->url(),
'sensitive' => (bool) $i->is_nsfw,
'atomUri' => $i->url(),
'inReplyToAtomUri' => null,
'attachment' => [
'attachment' => [
// TODO: support more than 1 attachment
[
'type' => 'Document',
'type' => 'Document',
'mediaType' => $i->firstMedia()->mime,
'url' => $i->firstMedia()->url(),
'name' => null
]
'url' => $i->firstMedia()->url(),
'name' => null,
],
],
'tag' => []
]
'tag' => [],
],
];
return $item;
});
return [
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $profile->permalink('/outbox'),
'type' => 'OrderedCollection',
'totalItems' => $count,
'orderedItems' => $statuses
return $item;
});
return [
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $profile->permalink('/outbox'),
'type' => 'OrderedCollection',
'totalItems' => $count,
'orderedItems' => $statuses,
];
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,16 +5,12 @@
* @author Nick Pope <nick@nickpope.me.uk>
* @copyright Copyright © 2010, Mike Cochrane, Nick Pope
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter.Text
*/
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
* hashtags.
@ -27,24 +23,22 @@ use App\Util\Lexer\StringUtils;
* @author Nick Pope <nick@nickpope.me.uk>
* @copyright Copyright © 2010, Mike Cochrane, Nick Pope
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter.Text
*/
class Extractor extends Regex
{
/**
* @var boolean
* @var bool
*/
protected $extractURLWithoutProtocol = true;
/**
* Provides fluent method chaining.
*
* @param string $tweet The tweet to be converted.
* @param string $tweet The tweet to be converted.
*
* @see __construct()
*
* @return Extractor
* @return Extractor
*/
public static function create($tweet = null)
{
@ -56,7 +50,7 @@ class Extractor extends Regex
*
* 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)
{
@ -67,29 +61,32 @@ class Extractor extends Regex
* Extracts all parts of a tweet and returns an associative array containing
* the extracted elements.
*
* @param string $tweet The tweet to extract.
* @return array The elements in the tweet.
* @param string $tweet The tweet to extract.
*
* @return array The elements in the tweet.
*/
public function extract($tweet = null)
{
if (is_null($tweet)) {
$tweet = $this->tweet;
}
return array(
'hashtags' => $this->extractHashtags($tweet),
'urls' => $this->extractURLs($tweet),
'mentions' => $this->extractMentionedUsernames($tweet),
'replyto' => $this->extractRepliedUsernames($tweet),
return [
'hashtags' => $this->extractHashtags($tweet),
'urls' => $this->extractURLs($tweet),
'mentions' => $this->extractMentionedUsernames($tweet),
'replyto' => $this->extractRepliedUsernames($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),
);
];
}
/**
* 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
*/
public function extractEntitiesWithIndices($tweet = null)
@ -97,63 +94,70 @@ class Extractor extends Regex
if (is_null($tweet)) {
$tweet = $this->tweet;
}
$entities = array();
$entities = [];
$entities = array_merge($entities, $this->extractURLsWithIndices($tweet));
$entities = array_merge($entities, $this->extractHashtagsWithIndices($tweet, false));
$entities = array_merge($entities, $this->extractMentionsOrListsWithIndices($tweet));
$entities = array_merge($entities, $this->extractCashtagsWithIndices($tweet));
$entities = $this->removeOverlappingEntities($entities);
return $entities;
}
/**
* Extracts all the hashtags from the tweet.
*
* @param string $tweet The tweet to extract.
* @return array The hashtag elements in the tweet.
* @param string $tweet The tweet to extract.
*
* @return array The hashtag elements in the tweet.
*/
public function extractHashtags($tweet = null)
{
$hashtagsOnly = array();
$hashtagsOnly = [];
$hashtagsWithIndices = $this->extractHashtagsWithIndices($tweet);
foreach ($hashtagsWithIndices as $hashtagWithIndex) {
$hashtagsOnly[] = $hashtagWithIndex['hashtag'];
}
return $hashtagsOnly;
}
/**
* Extracts all the cashtags from the tweet.
*
* @param string $tweet The tweet to extract.
* @return array The cashtag elements in the tweet.
* @param string $tweet The tweet to extract.
*
* @return array The cashtag elements in the tweet.
*/
public function extractCashtags($tweet = null)
{
$cashtagsOnly = array();
$cashtagsOnly = [];
$cashtagsWithIndices = $this->extractCashtagsWithIndices($tweet);
foreach ($cashtagsWithIndices as $cashtagWithIndex) {
$cashtagsOnly[] = $cashtagWithIndex['cashtag'];
}
return $cashtagsOnly;
}
/**
* Extracts all the URLs from the tweet.
*
* @param string $tweet The tweet to extract.
* @return array The URL elements in the tweet.
* @param string $tweet The tweet to extract.
*
* @return array The URL elements in the tweet.
*/
public function extractURLs($tweet = null)
{
$urlsOnly = array();
$urlsOnly = [];
$urlsWithIndices = $this->extractURLsWithIndices($tweet);
foreach ($urlsWithIndices as $urlWithIndex) {
$urlsOnly[] = $urlWithIndex['url'];
}
return $urlsOnly;
}
@ -162,21 +166,23 @@ class Extractor extends Regex
*
* A mention is an occurrence of a username anywhere in a tweet.
*
* @param string $tweet The tweet to extract.
* @return array The usernames elements in the tweet.
* @param string $tweet The tweet to extract.
*
* @return array The usernames elements in the tweet.
*/
public function extractMentionedScreennames($tweet = null)
{
$usernamesOnly = array();
$usernamesOnly = [];
$mentionsWithIndices = $this->extractMentionsOrListsWithIndices($tweet);
foreach ($mentionsWithIndices as $mentionWithIndex) {
$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;
}
$usernamesOnly[] = $screen_name;
}
return $usernamesOnly;
}
@ -185,12 +191,14 @@ class Extractor extends Regex
*
* 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
*/
public function extractMentionedUsernames($tweet)
{
$this->tweet = $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.
*
* @param string $tweet The tweet to extract.
* @return array The usernames replied to in a tweet.
* @param string $tweet The tweet to extract.
*
* @return array The usernames replied to in a tweet.
*/
public function extractReplyScreenname($tweet = null)
{
@ -208,10 +217,11 @@ class Extractor extends Regex
$tweet = $this->tweet;
}
$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])) {
$matched = false;
}
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.
*
* @return array The usernames replied to in a tweet.
* @return array The usernames replied to in a tweet.
*
* @deprecated since version 1.1.0
*/
public function extractRepliedUsernames()
@ -231,9 +242,10 @@ class Extractor extends Regex
/**
* Extracts all the hashtags and the indices they occur at from the tweet.
*
* @param string $tweet The tweet to extract.
* @param boolean $checkUrlOverlap if true, check if extracted hashtags overlap URLs and remove overlapping ones
* @return array The hashtag elements in the tweet.
* @param string $tweet The tweet to extract.
* @param bool $checkUrlOverlap if true, check if extracted hashtags overlap URLs and remove overlapping ones
*
* @return array The hashtag elements in the tweet.
*/
public function extractHashtagsWithIndices($tweet = null, $checkUrlOverlap = true)
{
@ -242,36 +254,36 @@ class Extractor extends Regex
}
if (!preg_match('/[#]/iu', $tweet)) {
return array();
return [];
}
preg_match_all(self::$patterns['valid_hashtag'], $tweet, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
$tags = array();
$tags = [];
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];
$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])) {
continue;
}
$tags[] = array(
$tags[] = [
'hashtag' => $hashtag[0],
'indices' => array($start_position, $end_position)
);
'indices' => [$start_position, $end_position],
];
}
if (!$checkUrlOverlap) {
return $tags;
}
# check url overlap
// check url overlap
$urls = $this->extractURLsWithIndices($tweet);
$entities = $this->removeOverlappingEntities(array_merge($tags, $urls));
$validTags = array();
$validTags = [];
foreach ($entities as $entity) {
if (empty($entity['hashtag'])) {
continue;
@ -285,8 +297,9 @@ class Extractor extends Regex
/**
* Extracts all the cashtags and the indices they occur at from the tweet.
*
* @param string $tweet The tweet to extract.
* @return array The cashtag elements in the tweet.
* @param string $tweet The tweet to extract.
*
* @return array The cashtag elements in the tweet.
*/
public function extractCashtagsWithIndices($tweet = null)
{
@ -295,25 +308,25 @@ class Extractor extends Regex
}
if (!preg_match('/\$/iu', $tweet)) {
return array();
return [];
}
preg_match_all(self::$patterns['valid_cashtag'], $tweet, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
$tags = array();
$tags = [];
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];
$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])) {
continue;
}
$tags[] = array(
$tags[] = [
'cashtag' => $cash_text[0],
'indices' => array($start_position, $end_position)
);
'indices' => [$start_position, $end_position],
];
}
return $tags;
@ -322,8 +335,9 @@ class Extractor extends Regex
/**
* Extracts all the URLs and the indices they occur at from the tweet.
*
* @param string $tweet The tweet to extract.
* @return array The URLs elements in the tweet.
* @param string $tweet The tweet to extract.
*
* @return array The URLs elements in the tweet.
*/
public function extractURLsWithIndices($tweet = null)
{
@ -333,14 +347,14 @@ class Extractor extends Regex
$needle = $this->extractURLWithoutProtocol() ? '.' : ':';
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);
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];
$end_position = $start_position + StringUtils::strlen($url[0]);
@ -364,13 +378,13 @@ class Extractor extends Regex
$ascii_end_position = 0;
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_end_position = $ascii_start_position + StringUtils::strlen($asciiDomain[0]);
$last_url = array(
'url' => $asciiDomain[0],
'indices' => array($start_position + $ascii_start_position, $start_position + $ascii_end_position),
);
$last_url = [
'url' => $asciiDomain[0],
'indices' => [$start_position + $ascii_start_position, $start_position + $ascii_end_position],
];
if (!empty($path)
|| preg_match(self::$patterns['valid_special_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.
if (!empty($path)) {
// 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;
}
} else {
@ -395,10 +409,10 @@ class Extractor extends Regex
$url = $tcoUrlMatches[0];
$end_position = $start_position + StringUtils::strlen($url);
}
$urls[] = array(
'url' => $url,
'indices' => array($start_position, $end_position),
);
$urls[] = [
'url' => $url,
'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.
*
* @param string $tweet The tweet to extract.
* @return array The username elements in the tweet.
* @param string $tweet The tweet to extract.
*
* @return array The username elements in the tweet.
*/
public function extractMentionedScreennamesWithIndices($tweet = null)
{
@ -417,7 +432,7 @@ class Extractor extends Regex
$tweet = $this->tweet;
}
$usernamesOnly = array();
$usernamesOnly = [];
$mentions = $this->extractMentionsOrListsWithIndices($tweet);
foreach ($mentions as $mention) {
if (isset($mention['list_slug'])) {
@ -425,13 +440,15 @@ class Extractor extends Regex
}
$usernamesOnly[] = $mention;
}
return $usernamesOnly;
}
/**
* 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
*/
public function extractMentionedUsernamesWithIndices()
@ -442,8 +459,9 @@ class Extractor extends Regex
/**
* Extracts all the usernames and the indices they occur at from the tweet.
*
* @param string $tweet The tweet to extract.
* @return array The username elements in the tweet.
* @param string $tweet The tweet to extract.
*
* @return array The username elements in the tweet.
*/
public function extractMentionsOrListsWithIndices($tweet = null)
{
@ -452,21 +470,21 @@ class Extractor extends Regex
}
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);
$results = array();
$results = [];
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];
$end_position = $start_position + StringUtils::strlen($at[0]) + StringUtils::strlen($username[0]);
$entity = array(
$entity = [
'screen_name' => $username[0],
'list_slug' => $list_slug[0],
'indices' => array($start_position, $end_position),
);
'list_slug' => $list_slug[0],
'indices' => [$start_position, $end_position],
];
if (preg_match(self::$patterns['end_mention_match'], $outer[0])) {
continue;
@ -485,7 +503,8 @@ class Extractor extends Regex
/**
* 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
*/
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
*/
public function extractURLWithoutProtocol($flag = null)
@ -505,6 +525,7 @@ class Extractor extends Regex
return $this->extractURLWithoutProtocol;
}
$this->extractURLWithoutProtocol = (bool) $flag;
return $this;
}
@ -513,12 +534,13 @@ class Extractor extends Regex
* This returns a new array with no overlapping entities.
*
* @param array $entities
*
* @return array
*/
public function removeOverlappingEntities($entities)
{
$result = array();
usort($entities, array($this, 'sortEntites'));
$result = [];
usort($entities, [$this, 'sortEntites']);
$prev = null;
foreach ($entities as $entity) {
@ -528,14 +550,16 @@ class Extractor extends Regex
$prev = $entity;
$result[] = $entity;
}
return $result;
}
/**
* sort by entity start index
* sort by entity start index.
*
* @param array $a
* @param array $b
*
* @return int
*/
protected function sortEntites($a, $b)
@ -543,6 +567,7 @@ class Extractor extends Regex
if ($a['indices'][0] == $b['indices'][0]) {
return 0;
}
return ($a['indices'][0] < $b['indices'][0]) ? -1 : 1;
}
}

View file

@ -2,35 +2,36 @@
namespace App\Util\Lexer;
class Hashtag {
class Hashtag
{
public static function getHashtags($status)
{
$hashtags = false;
preg_match_all("/(?<!&)(#\w+)/u", $status, $matches);
if ($matches) {
$res = array_count_values($matches[0]);
$hashtags = array_keys($res);
}
public static function getHashtags($status)
{
$hashtags = false;
preg_match_all("/(?<!&)(#\w+)/u", $status, $matches);
if ($matches) {
$res = array_count_values($matches[0]);
$hashtags = array_keys($res);
}
return $hashtags;
}
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 $hashtags;
}
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>
* @copyright Copyright © 2010, Nick Pope
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter.Text
*/
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.
* 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>
* @copyright Copyright © 2010, Nick Pope
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter.Text
*/
class HitHighlighter extends Regex
{
/**
* The tag to surround hits with.
*
* @var string
* @var string
*/
protected $tag = 'em';
/**
* Provides fluent method chaining.
*
* @param string $tweet The tweet to be hit highlighted.
* @param bool $full_encode Whether to encode all special characters.
* @param string $tweet The tweet to be hit highlighted.
* @param bool $full_encode Whether to encode all special characters.
*
* @see __construct()
*
* @return HitHighlighter
* @return HitHighlighter
*/
public static function create($tweet = null, $full_encode = false)
{
@ -59,9 +53,9 @@ class HitHighlighter extends Regex
*
* @see htmlspecialchars()
*
* @param string $tweet The tweet to be hit highlighted.
* @param bool $escape Whether to escape the tweet (default: true).
* @param bool $full_encode Whether to encode all special characters.
* @param string $tweet The tweet to be hit highlighted.
* @param bool $escape Whether to escape the tweet (default: true).
* @param bool $full_encode Whether to encode all special characters.
*/
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'.
*
* @return string The tag name.
* @return string The tag name.
*/
public function getTag()
{
@ -89,26 +83,27 @@ class HitHighlighter extends Regex
/**
* 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)
{
$this->tag = $v;
return $this;
}
/**
* Hit highlights the tweet.
*
* @param string $tweet The tweet to be hit highlighted.
* @param array $hits An array containing the start and end index pairs
* for the highlighting.
* @param string $tweet The tweet to be hit highlighted.
* @param array $hits An array containing the start and end index pairs
* for the highlighting.
* @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)
{
@ -119,8 +114,8 @@ class HitHighlighter extends Regex
return $tweet;
}
$highlightTweet = '';
$tags = array('<' . $this->tag . '>', '</' . $this->tag . '>');
# Check whether we can simply replace or whether we need to chunk...
$tags = ['<'.$this->tag.'>', '</'.$this->tag.'>'];
// Check whether we can simply replace or whether we need to chunk...
if (strpos($tweet, '<') === false) {
$ti = 0; // tag increment (for added tags)
$highlightTweet = $tweet;
@ -137,12 +132,12 @@ class HitHighlighter extends Regex
$chunk_cursor = 0;
$offset = 0;
$start_in_chunk = false;
# Flatten the multidimensional hits array:
$hits_flat = array();
// Flatten the multidimensional hits array:
$hits_flat = [];
foreach ($hits as $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++) {
$hit = $hits_flat[$index];
$tag = $tags[$index % 2];
@ -154,7 +149,7 @@ class HitHighlighter extends Regex
$placed = true;
}
if (isset($chunks[$chunk_index + 1])) {
$highlightTweet .= '<' . $chunks[$chunk_index + 1] . '>';
$highlightTweet .= '<'.$chunks[$chunk_index + 1].'>';
}
$offset += StringUtils::strlen($chunk);
$chunk_cursor = 0;
@ -164,12 +159,12 @@ class HitHighlighter extends Regex
}
if (!$placed && $chunk !== null) {
$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;
$start_in_chunk = ($index % 2 === 0);
$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) {
$highlightTweet .= $tag;
}
@ -179,20 +174,22 @@ class HitHighlighter extends Regex
$highlightTweet .= StringUtils::substr($chunk, $chunk_cursor);
}
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;
}
/**
* Hit highlights the tweet.
*
* @param array $hits An array containing the start and end index pairs
* for the highlighting.
* @param array $hits An array containing the start and end index pairs
* for the highlighting.
*
* @return string The hit highlighted tweet.
*
* @return string The hit highlighted tweet.
* @deprecated since version 1.1.0
*/
public function addHitHighlighting(array $hits)

View file

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

View file

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

View file

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

View file

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

View file

@ -4,17 +4,12 @@
* @author Nick Pope <nick@nickpope.me.uk>
* @copyright Copyright © 2010, Nick Pope
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter.Text
*/
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.
*
@ -25,34 +20,31 @@ use App\Util\Lexer\StringUtils;
* @author Nick Pope <nick@nickpope.me.uk>
* @copyright Copyright © 2010, Nick Pope
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
* @package Twitter.Text
*/
class Validator extends Regex
{
/**
* The maximum length of a tweet.
*
* @var int
* @var int
*/
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;
/**
* 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;
/**
*
* @var Extractor
*/
protected $extractor = null;
@ -60,12 +52,12 @@ class Validator extends Regex
/**
* Provides fluent method chaining.
*
* @param string $tweet The tweet to be validated.
* @param mixed $config Setup short URL length from Twitter API /help/configuration response.
* @param string $tweet The tweet to be validated.
* @param mixed $config Setup short URL length from Twitter API /help/configuration response.
*
* @see __construct()
*
* @return Validator
* @return Validator
*/
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.
*
* @param string $tweet The tweet to validate.
* @param string $tweet The tweet to validate.
*/
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
*
* @return Validator
*
* @link https://dev.twitter.com/docs/api/1/get/help/configuration
*/
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
*
* @return Validator
*/
public function setShortUrlLength($length)
{
$this->short_url_length = intval($length);
return $this;
}
/**
* Get the length of a short URL beginning with http:
* Get the length of a short URL beginning with http:.
*
* @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
*
* @return Validator
*/
public function setShortUrlLengthHttps($length)
{
$this->short_url_length_https = intval($length);
return $this;
}
/**
* Get the length of a short URL beginning with https:
* Get the length of a short URL beginning with https:.
*
* @return int
*/
@ -164,7 +162,8 @@ class Validator extends Regex
* Check whether a tweet is valid.
*
* @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)
{
@ -181,13 +180,15 @@ class Validator extends Regex
if (preg_match(self::$patterns['invalid_characters'], $tweet)) {
return false;
}
return true;
}
/**
* 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
*/
public function validateTweet()
@ -199,7 +200,8 @@ class Validator extends Regex
* Check whether a username is valid.
*
* @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)
{
@ -211,13 +213,15 @@ class Validator extends Regex
return false;
}
$extracted = $this->extractor->extractMentionedScreennames($username);
return count($extracted) === 1 && $extracted[0] === substr($username, 1);
}
/**
* 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
*/
public function validateUsername()
@ -229,7 +233,8 @@ class Validator extends Regex
* Check whether a list is valid.
*
* @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)
{
@ -242,13 +247,15 @@ class Validator extends Regex
}
preg_match(self::$patterns['valid_mentions_or_lists'], $list, $matches);
$matches = array_pad($matches, 5, '');
return isset($matches) && $matches[1] === '' && $matches[4] && !empty($matches[4]) && $matches[5] === '';
}
/**
* 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
*/
public function validateList()
@ -260,7 +267,8 @@ class Validator extends Regex
* Check whether a hashtag is valid.
*
* @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)
{
@ -272,13 +280,15 @@ class Validator extends Regex
return false;
}
$extracted = $this->extractor->extractHashtags($hashtag);
return count($extracted) === 1 && $extracted[0] === substr($hashtag, 1);
}
/**
* 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
*/
public function validateHashtag()
@ -289,11 +299,11 @@ class Validator extends Regex
/**
* Check whether a URL is valid.
*
* @param string $url The url to validate.
* @param boolean $unicode_domains Consider the domain to be unicode.
* @param boolean $require_protocol Require a protocol for valid domain?
* @param string $url The url to validate.
* @param bool $unicode_domains Consider the domain to be unicode.
* @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)
{
@ -310,25 +320,27 @@ class Validator extends Regex
return false;
}
list($scheme, $authority, $path, $query, $fragment) = array_pad($matches, 5, '');
# Check scheme, path, query, fragment:
// Check scheme, path, query, fragment:
if (($require_protocol && !(
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($fragment, self::$patterns['validate_url_fragment'], true)) {
return false;
}
# Check authority:
// Check authority:
$authority_pattern = $unicode_domains ? 'validate_url_unicode_authority' : 'validate_url_authority';
return self::isValidMatch($authority, self::$patterns[$authority_pattern]);
}
/**
* Check whether a URL is valid.
*
* @param boolean $unicode_domains Consider the domain to be unicode.
* @param boolean $require_protocol Require a protocol for valid domain?
* @param bool $unicode_domains Consider the domain to be unicode.
* @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
*/
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.
*
* @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)
{
@ -353,13 +366,15 @@ class Validator extends Regex
$length += $x['indices'][0] - $x['indices'][1];
$length += stripos($x['url'], 'https://') === 0 ? $this->short_url_length_https : $this->short_url_length;
}
return $length;
}
/**
* 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
*/
public function getLength()
@ -370,17 +385,17 @@ class Validator extends Regex
/**
* A helper function to check for a valid match. Used in URL validation.
*
* @param string $string The subject string to test.
* @param string $pattern The pattern to match against.
* @param boolean $optional Whether a match is compulsory or not.
* @param string $string The subject string to test.
* @param string $pattern The pattern to match against.
* @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)
{
$found = preg_match($pattern, $string, $matches);
if (!$optional) {
return (($string || $string === '') && $found && $matches[0] === $string);
return ($string || $string === '') && $found && $matches[0] === $string;
} else {
return !(($string || $string === '') && (!$found || $matches[0] !== $string));
}

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