-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
00627e4
commit 30074b9
Showing
11 changed files
with
468 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?php | ||
|
||
namespace Obydul\Larable\Vote\Events; | ||
|
||
use Obydul\Larable\Vote\Vote; | ||
|
||
class Event | ||
{ | ||
public Vote $vote; | ||
|
||
/** | ||
* Event constructor. | ||
*/ | ||
public function __construct(Vote $vote) | ||
{ | ||
$this->vote = $vote; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<?php | ||
|
||
namespace Obydul\Larable\Vote\Events; | ||
|
||
class VoteCancelled extends Event | ||
{ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<?php | ||
|
||
namespace Obydul\Larable\Vote\Events; | ||
|
||
class Voted extends Event | ||
{ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
<?php | ||
|
||
namespace Obydul\Larable\Vote\Traits; | ||
|
||
use Illuminate\Database\Eloquent\Builder; | ||
use Illuminate\Database\Eloquent\Model; | ||
|
||
/** | ||
* @property \Illuminate\Database\Eloquent\Collection $voters | ||
* @property \Illuminate\Database\Eloquent\Collection $votes | ||
* | ||
* @method bool relationLoaded(string $name) | ||
* @method static withVotesAttributes() | ||
* @method static withTotalVotes() | ||
* @method static withTotalUpvotes() | ||
* @method static withTotalDownvotes() | ||
*/ | ||
trait Votable | ||
{ | ||
public function hasBeenVotedBy(Model $user): bool | ||
{ | ||
if (\is_a($user, config('auth.providers.users.model'))) { | ||
if ($this->relationLoaded('voters')) { | ||
return $this->voters->contains($user); | ||
} | ||
|
||
return ($this->relationLoaded('votes') ? $this->votes : $this->votes()) | ||
->where(\config('larable_vote.user_foreign_key'), $user->getKey())->count() > 0; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public function votes(): \Illuminate\Database\Eloquent\Relations\MorphMany | ||
{ | ||
return $this->morphMany(config('larable_vote.vote_model'), 'votable'); | ||
} | ||
|
||
public function upvotes(): \Illuminate\Database\Eloquent\Relations\MorphMany | ||
{ | ||
return $this->votes()->where('votes', '>', 0); | ||
} | ||
|
||
public function downvotes(): \Illuminate\Database\Eloquent\Relations\MorphMany | ||
{ | ||
return $this->votes()->where('votes', '<', 0); | ||
} | ||
|
||
public function voters() | ||
{ | ||
return $this->belongsToMany( | ||
config('auth.providers.users.model'), | ||
config('larable_vote.votes_table'), | ||
'votable_id', | ||
config('larable_vote.user_foreign_key') | ||
)->where('votable_type', $this->getMorphClass())->withPivot(['votes']); | ||
} | ||
|
||
public function upvoters(): \Illuminate\Database\Eloquent\Relations\BelongsToMany | ||
{ | ||
return $this->voters()->where('votes', '>', 0); | ||
} | ||
|
||
public function downvoters(): \Illuminate\Database\Eloquent\Relations\BelongsToMany | ||
{ | ||
return $this->voters()->where('votes', '<', 0); | ||
} | ||
|
||
public function appendsVotesAttributes($attributes = ['total_votes', 'total_upvotes', 'total_downvotes']) | ||
{ | ||
$this->append($attributes); | ||
|
||
return $this; | ||
} | ||
|
||
public function getTotalVotesAttribute() | ||
{ | ||
return (int) ($this->attributes['total_votes'] ?? $this->totalVotes()); | ||
} | ||
|
||
public function getTotalUpvotesAttribute() | ||
{ | ||
return abs($this->attributes['total_upvotes'] ?? $this->totalUpvotes()); | ||
} | ||
|
||
public function getTotalDownvotesAttribute() | ||
{ | ||
return abs($this->attributes['total_downvotes'] ?? $this->totalDownvotes()); | ||
} | ||
|
||
public function totalVotes() | ||
{ | ||
return $this->votes()->sum('votes'); | ||
} | ||
|
||
public function totalUpvotes() | ||
{ | ||
return $this->votes()->where('votes', '>', 0)->sum('votes'); | ||
} | ||
|
||
public function totalDownvotes() | ||
{ | ||
return $this->votes()->where('votes', '<', 0)->sum('votes'); | ||
} | ||
|
||
public function scopeWithTotalVotes(Builder $builder): Builder | ||
{ | ||
return $builder->withSum(['votes as total_votes' => fn ($q) => $q->select(\DB::raw('COALESCE(SUM(votes), 0)')), | ||
], 'votes'); | ||
} | ||
|
||
public function scopeWithTotalUpvotes(Builder $builder): Builder | ||
{ | ||
return $builder->withSum(['votes as total_upvotes' => fn ($q) => $q->where('votes', '>', 0)->select(\DB::raw('COALESCE(SUM(votes), 0)')), | ||
], 'votes'); | ||
} | ||
|
||
public function scopeWithTotalDownvotes(Builder $builder): Builder | ||
{ | ||
return $builder->withSum(['votes as total_downvotes' => fn ($q) => $q->where('votes', '<', 0)->select(\DB::raw('COALESCE(SUM(votes), 0)')), | ||
], 'votes'); | ||
} | ||
|
||
public function scopeWithVotesAttributes(Builder $builder) | ||
{ | ||
$this->scopeWithTotalVotes($builder); | ||
$this->scopeWithTotalUpvotes($builder); | ||
$this->scopeWithTotalDownvotes($builder); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
<?php | ||
|
||
namespace Obydul\Larable\Vote\Traits; | ||
|
||
use Illuminate\Contracts\Pagination\Paginator; | ||
use Illuminate\Database\Eloquent\Model; | ||
use Illuminate\Pagination\LengthAwarePaginator; | ||
use Illuminate\Support\Collection; | ||
use Overtrue\LaravelVote\Vote; | ||
|
||
/** | ||
* @property \Illuminate\Database\Eloquent\Collection $votes | ||
*/ | ||
trait Voter | ||
{ | ||
public function vote(Model $object, int $votes = 1): Vote | ||
{ | ||
return $votes > 0 ? $this->upvote($object, $votes) : $this->downvote($object, $votes); | ||
} | ||
|
||
public function upvote(Model $object, int $votes = 1) | ||
{ | ||
/* @var Votable|Model $object */ | ||
if ($this->hasVoted($object)) { | ||
$this->cancelVote($object); | ||
} | ||
|
||
$vote = app(config('larable_vote.vote_model')); | ||
$vote->{config('larable_vote.user_foreign_key')} = $this->getKey(); | ||
$vote->votes = abs($votes); | ||
$object->votes()->save($vote); | ||
|
||
return $vote; | ||
} | ||
|
||
public function downvote(Model $object, int $votes = 1) | ||
{ | ||
/* @var Votable|Model $object */ | ||
if ($this->hasVoted($object)) { | ||
$this->cancelVote($object); | ||
} | ||
|
||
$vote = app(config('larable_vote.vote_model')); | ||
$vote->{config('larable_vote.user_foreign_key')} = $this->getKey(); | ||
$vote->votes = abs($votes) * -1; | ||
$object->votes()->save($vote); | ||
|
||
return $vote; | ||
} | ||
|
||
public function attachVoteStatus(Model|Collection|Paginator|LengthAwarePaginator|array $votables): Collection|Model | ||
{ | ||
$returnFirst = false; | ||
|
||
switch (true) { | ||
case $votables instanceof Model: | ||
$returnFirst = true; | ||
$votables = \collect([$votables]); | ||
break; | ||
case $votables instanceof LengthAwarePaginator: | ||
$votables = $votables->getCollection(); | ||
break; | ||
case $votables instanceof Paginator: | ||
$votables = \collect($votables->items()); | ||
break; | ||
case \is_array($votables): | ||
$votables = \collect($votables); | ||
break; | ||
} | ||
|
||
$voterVoted = $this->votes()->get()->keyBy(function ($item) { | ||
return \sprintf('%s-%s', $item->votable_type, $item->votable_id); | ||
}); | ||
|
||
$votables->map(function (Model $votable) use ($voterVoted) { | ||
if (\in_array(Votable::class, \class_uses($votable))) { | ||
$key = \sprintf('%s-%s', $votable->getMorphClass(), $votable->getKey()); | ||
$votable->setAttribute('has_voted', $voterVoted->has($key)); | ||
$votable->setAttribute('has_upvoted', $voterVoted->has($key) && $voterVoted->get($key)->is_up_vote); | ||
$votable->setAttribute('has_downvoted', $voterVoted->has($key) && $voterVoted->get($key)->is_down_vote); | ||
} | ||
}); | ||
|
||
return $returnFirst ? $votables->first() : $votables; | ||
} | ||
|
||
public function cancelVote(Model $object): bool | ||
{ | ||
/* @var Votable|Model $object */ | ||
$relation = $object->votes() | ||
->where('votable_id', $object->getKey()) | ||
->where('votable_type', $object->getMorphClass()) | ||
->where(config('larable_vote.user_foreign_key'), $this->getKey()) | ||
->first(); | ||
|
||
if ($relation) { | ||
$relation->delete(); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
public function hasVoted(Model $object): bool | ||
{ | ||
return ($this->relationLoaded('votes') ? $this->votes : $this->votes()) | ||
->where('votable_id', $object->getKey()) | ||
->where('votable_type', $object->getMorphClass()) | ||
->count() > 0; | ||
} | ||
|
||
public function votes(): \Illuminate\Database\Eloquent\Relations\HasMany | ||
{ | ||
return $this->hasMany(config('larable_vote.vote_model'), config('larable_vote.user_foreign_key'), $this->getKeyName()); | ||
} | ||
|
||
public function upvotes(): \Illuminate\Database\Eloquent\Relations\HasMany | ||
{ | ||
return $this->votes()->where('votes', '>', 0); | ||
} | ||
|
||
public function downvotes(): \Illuminate\Database\Eloquent\Relations\HasMany | ||
{ | ||
return $this->votes()->where('votes', '<', 0); | ||
} | ||
|
||
public function getVotedItems(string $model) | ||
{ | ||
return app($model)->whereHas( | ||
'voters', | ||
function ($q) { | ||
return $q->where(config('larable_vote.user_foreign_key'), $this->getKey()); | ||
} | ||
); | ||
} | ||
} |
Oops, something went wrong.