Skip to content

Commit

Permalink
added overtrue/laravel-vote package
Browse files Browse the repository at this point in the history
  • Loading branch information
mdobydullah committed Mar 25, 2023
1 parent 00627e4 commit 30074b9
Show file tree
Hide file tree
Showing 11 changed files with 468 additions and 2 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ The available morphable packages/features.
| Follow | [laravel-follow:v5.1.0](https://github.com/overtrue/laravel-follow) | User follow unfollow feature . |
| Subscribe | [laravel-subscribe:v4.2.0](https://github.com/overtrue/laravel-subscribe) | User subscribe unsubscribe feature. |
| Favorite | [laravel-favorite:v5.1.0](https://github.com/overtrue/laravel-favorite) | User favorite unfavorite feature. |
| Vote | [laravel-vote:v3.1.0](https://github.com/overtrue/laravel-vote) | User voting feature. |

## Cleanup Unused Services

Expand Down Expand Up @@ -64,7 +65,7 @@ This example will remove all services other than "Like" and "Follow" when ```com
**IMPORTANT**: If you add any services back in ```composer.json```, you will need to remove the ```vendor/obydul/larable``` directory explicitly for the change you made to have effect:

```
rm -r obydul/larable
rm -r vendor/obydul/larable
composer update
```

Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,6 @@
"scripts-descriptions": {
"check-style": "Run style checks (only dry run - no fixing!).",
"fix-style": "Run style checks and fix violations."
}
},
"minimum-stability": "dev"
}
1 change: 1 addition & 0 deletions src/Helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public function services(): array
'Follow',
'Subscribe',
'Favorite',
'Vote',
];

// read root composer.json
Expand Down
18 changes: 18 additions & 0 deletions src/Vote/Events/Event.php
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;
}
}
7 changes: 7 additions & 0 deletions src/Vote/Events/VoteCancelled.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Obydul\Larable\Vote\Events;

class VoteCancelled extends Event
{
}
7 changes: 7 additions & 0 deletions src/Vote/Events/Voted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Obydul\Larable\Vote\Events;

class Voted extends Event
{
}
130 changes: 130 additions & 0 deletions src/Vote/Traits/Votable.php
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);
}
}
135 changes: 135 additions & 0 deletions src/Vote/Traits/Voter.php
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());
}
);
}
}
Loading

0 comments on commit 30074b9

Please sign in to comment.