Empty model in Eloquent Global scope? - php

I am trying to determine if a user has the ability to update a model in a global scope, but I am using permission prefixes that I normally get through a relation.
The apply code is as follows:
public function apply(Builder $builder, Model $model)
{
$user = getUser();
if ($user === null || $user->cannot('update', $model)) {
$builder->where('active', '=', 1);
}
}
When I dd($model) the model is not actually instantiated so when I do my update permission check in my policy:
public function update(User $user, Item $item)
{
return $user->hasAnyPermission(['edit-things', $this->prefix($item) . "-edit-item"]);
}
Where the prefix function looks like:
private function prefix(Item $item = null)
{
if ($item !== null) {
return $item->parentRelation->roles_permission_prefix;
}
$parentRelation= ParentRelation::findOrFail(request('parent_relation_id'));
return $parentRelation->roles_permission_prefix;
}
It all fails due to their actaully not being a relationship. Any ideas?
Quick Edit: I am using the Spatie Permissions library if that is pertinent.

The only function that would break on a missing relationship is the prefix() function.
You can prevent prefix() from being called by checking if both $model and $model->parentRelation are null too in your list of conditions:
public function apply(Builder $builder, Model $model)
{
$user = getUser();
if ( $user === null
|| $model === null
|| (method_exists($model, 'parent_relation') && !$model->parentRelation)
|| $user->cannot('update', $model)
) {
$builder->where('active', '=', 1);
}
}

Related

Laravel 5.6 apply pivot scope in relationship

I need to apply a scope form a pivot class in a morphedByMany relationship included in a User class. This is the relationship:
public function nodes($nodeClass)
{
return
$this->morphedByMany(
$nodeClass,
'node',
'users_permissions',
'user_id',
'node_id',
'id',
'id'
)
->using('App\Models\UserNode');
}
Then, I have a role scope in the pivot class (UserNode). That scope is not our code, it is from HasRoles Traits from spatie/laravel-permission package:
public function scopeRole(Builder $query, $roles, $guard = null): Builder
{
if ($roles instanceof Collection) {
$roles = $roles->all();
}
if (! is_array($roles)) {
$roles = [$roles];
}
$roles = array_map(function ($role) use ($guard) {
if ($role instanceof Role) {
return $role;
}
$method = is_numeric($role) ? 'findById' : 'findByName';
$guard = $guard ?: $this->getDefaultGuardName();
return $this->getRoleClass()->{$method}($role, $guard);
}, $roles);
return $query->whereHas('roles', function ($query) use ($roles) {
$query->where(function ($query) use ($roles) {
foreach ($roles as $role) {
$query->orWhere(config('permission.table_names.roles').'.id', $role->id);
}
});
});
}
When I try to get specific class of nodes inside User class like this it works perfectly:
$nodes = $this->nodes($nodeClass)->get();
However, I don't know how I can apply properly the scope 'role' to the pivot class of the relation because wherePivot or wherePivotIn does not accept scopes, relations or closures. I have also tried other things with the same result like:
$nodes = $this->nodes($nodeClass)->whereHas('pivot', function($q) use ($roles) {
$q->role($roles);
})->get();
Is there any way to apply the pivot class' scope to this relation?
Thanks in advance

Laravel Eloquent relation between models

I need help with querying the result of relation between models. Two models are defined, User and Role model.
User:
public function roles()
{
return $this->belongsToMany('App\Role', 'role_users');
}
public function hasAccess(array $permissions) : bool
{
foreach ($this->roles as $role) {
if ($role->hasAccess($permissions)) {
return true;
}
}
return false;
}
public function inRole(string $slug)
{
return $this->roles()->where('slug', $slug)->count() == 1;
}
Role:
public function users()
{
return $this->hasMany('App\User', 'role_users');
}
public function hasAccess(array $permissions) : bool
{
foreach ($permissions as $permission) {
if ($this->hasPermission($permission)) {
return true;
}
}
return false;
}
private function hasPermission(string $permission) : bool
{
return $this->permissions[$permission] ?? false;
}
Also, a pivot table called role_users is defined. In the roles database several roles are pre-defined by seeding (Admin, Editor, Author).
I want to query the users by its roles, for example,
$editors = App\User::roles(2)->orderBy('name', 'asc')->get()
where in roles database the id of editor is 2. I got the error
PHP Deprecated: Non-static method App/User::roles()
How to solve this? Note: I'm using Laravel 5.6 and new to Laravel and framework. I tried to refer to docs but it confused me.
Thanks in advance.
You have to use whereHas(..):
$editors = \App\User::whereHas('roles', function ($q) {
$q->where('id', 2);
})->orderBy('name', 'asc')->get()
#devk is correct but here you have another way to get the data of user by role.
\App\Role::where('id', 2)->with('users')->get();

Sort by selectRaw relationship in Laravel (calculated count, sum ...)

I have a Question model which have morphMany Answer. Answer have also morphMany Answer and Rating
I added relationship to calculate and load Answer count and Answer rating sum :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use App\Facades\AuthManager;
class Answer extends Model {
protected $with = [
'user',
'cards',
'answers.user',
'answersCountRelation',
'ratingSumRelation'
];
protected $fillable = [
'text',
'user_id'
];
public function answerable() {
return $this->morphTo();
}
public function user() {
return $this->belongsTo(User::class);
}
public function answers() {
return $this->morphMany(Answer::class, 'answerable');
}
public function reports() {
return $this->morphMany(Report::class, 'reportable');
}
public function cards() {
return $this->hasMany(Card::class);
}
public function ratings() {
return $this->hasMany(Rating::class);
}
public function hasReported(User $user) {
return ($user != null && $this->reports()->where([
'user_id' => $user->id
])->first() != null);
}
public function hasRated(User $user) {
return ($user != null && $this->ratings()->where([
'user_id' => $user->id
])->first() != null);
}
public function answersCountRelation() {
return $this->answers()->selectRaw('answerable_id, count(*) as count')->groupBy('answerable_id');
}
public function getAnswersCountAttribute() {
return ($this->answersCountRelation->first() ? $this->answersCountRelation->first()->count : 0);
}
public function ratingSumRelation() {
return $this->ratings()->selectRaw('answer_id, SUM(power) as sum')->groupBy('answer_id');
}
public function getRatingSumAttribute() {
return ($this->ratingSumRelation->first() ? $this->ratingSumRelation->first()->sum : 0);
}
}
Now I wonder how can I load Question with Answer already sorted by answer count, rating, created_at etc
Is it possible to do it using the already eager loaded answersCountRelation or ratingSumRelation ?
Or do I have to do another request in Question->with(['answers' => function ($q) ...
Thx !

Model observer in Laravel 4, restore() triggering both restored() and updated()

I'm using an observer on models in Laravel 4 for the purposes of keeping historical records of changes made by each user. The code I'm currently using is as follows:
class BaseObserver {
public function __construct(){}
public function saving(Eloquent $model){}
public function saved(Eloquent $model){}
public function updating(Eloquent $model){}
public function updated(Eloquent $model)
{
$this->storeAuditData($model, 'update');
}
public function creating(Eloquent $model){}
public function created(Eloquent $model)
{
$this->storeAuditData($model, 'create');
}
public function deleting(Eloquent $model){}
public function deleted(Eloquent $model)
{
$this->storeAuditData($model, 'delete');
}
public function restoring(Eloquent $model){}
public function restored(Eloquent $model)
{
$this->storeAuditData($model, 'restore');
}
public function storeAuditData(Eloquent $model, $action)
{
$snapshot = array();
foreach ($model->fillable as $fieldName) {
$snapshot[$fieldName] = $model->$fieldName;
}
$auditData = new AuditData;
$auditData->model = get_class($model);
$auditData->rowId = $model->id;
$auditData->action = $action;
$auditData->user = Auth::user()->username;
$auditData->moment = date('Y-m-d H:i:s');
$auditData->snapshot = json_encode($snapshot);
$auditData->save();
}
}
This works fine, except when a restore() is performed, both the restored and updated methods get run, so I end up with two rows in the AuditData database table, when I only want one (the "restore").
Is there any way I can tell within the updated method whether the update is a restore or not, and only store the audit data if it is a stand-alone update and not a restore?
You could check if only the deleted_at column has been modified (is dirty)
public function updated(Eloquent $model)
{
if($model->isDirty($model->getDeletedAtColumn()) && count($model->getDirty()) == 1){
// only soft deleting column changed
}
else {
$this->storeAuditData($model, 'update');
}
}
While restoring, and using Laravel auto timestamps, the column updated_at will be modified along with deleted_at. To differentiate between 'restored' and 'updated' observer event, within updated method I use:
public function updated(Eloquent $model)
{
if ($model->isDirty($model->getDeletedAtColumn())
&& $model->{$model->getDeletedAtColumn()} === null
&& $model->getOriginal($model->getDeletedAtColumn()) !== null) {
// model was restored
}
}

Passing an array in the construct of a controller with laravel

I am wanting to find out how I can pass in roles as an array because when I try to do this in my construct of my controller it always seems to only be Administrator.
The following is my app/controllers/UserscController.php
class UsersController extends BaseController {
public function __construct()
{
parent::__construct();
$this->beforeFilter('role:Administrator,Owner');
}
}
The following is my app/filters.php
Route::filter('role', function($route, $request, $roles)
{
if(Auth::guest() !== true)
{
if(!empty($roles))
{
$roles = explode(',', $roles);
if(count($roles) > 0)
{
foreach($roles as $role)
{
if (Auth::user()->role->role_name == $role)
{
return;
}
}
}
}
return Redirect::to('/youshallnotpass');
}
});
You can set the roles as a parameter in your controller. To load the roles into your filter you would do this:
Route::filter('role', function($route, $request)
{
if(Auth::guest() !== true)
{
$roles = $route->parameter('roles');
if(!empty($roles))
{
foreach($roles as $role)
{
if (Auth::user()->role->role_name == $role)
{
return;
}
}
}
return Redirect::to('/youshallnotpass');
}
});
Note:
Before laraval 4.1 you would use: $roles = $route->getParameter('roles'); instead of $roles = $route->parameter('roles');
Hope this helps!
Laravel already explodes your filter parameters on the comma, to allow passing multiple parameters to a filter. So, in your case, you've actually passed two parameters to your filter: the first parameter has the value 'Administrator', and the second parameter has the value 'Owner'.
So, two quick options are:
Change the delimiter you're using in your string. In your controller: $this->beforeFilter('role:Administrator;Owner'); and then in your filter: $roles = explode(';', $roles);
Or, leave your controller code alone and use PHP's func_get_args() function in your filter:
Route::filter('role', function($route, $request)
{
if(Auth::guest() !== true)
{
$roles = array_slice(func_get_args(), 2);
foreach($roles as $role)
{
if (Auth::user()->role->role_name == $role)
{
return;
}
}
return Redirect::to('/youshallnotpass');
}
});

Categories