Authorization not working for Users Search - php

I'm using Laravel 5.1.26, including authorizations with policies. I have a basic ACL model where a user has a Role, of course an integer Role field.
My Roles are:
0 - Administrator (can perform any operation over users);
1- Operator (can't perform any operation over users);
2- Viewer (can't perform any operation over users).
I provide the option to perform a User's search. Only Administrators can perform this because my roles definition. So, I have a method in the UserController to display my results:
public function postSearch(Request $request)
{
$this->authorize('search', User::class);
$query = User::select('name', 'email')
->where('name', 'LIKE', "%$request->name%")
->where('email', 'LIKE', "%$request->email%")
->where('role', '=', "%$request->role%");
return $this->displayResult($query, $request);
}
As you see, the authorize method perform the validation, and the rest of code creates the result. Simple.
Problem is because if I search (using an Administrator user for all cases) for an Operator user, then all my operations are disabled: create, delete, edit, etc. Same behaviour if I search for a Viewer user.
But if I search for an Administrator user, then all my operations are enabled!
So, I guess that the authorize method is receiving the user found! Not the user authenticated. How can I solve this? I'm passing the User::class because if I don't pass anything, then my Policies don't work (therefore, I followed the last comment here https://laracasts.com/discuss/channels/laravel/laravel-511s-new-authorization-doesnt-read-policies/?page=2).
Thanks.

After perfom another Search option on my application, I found that Eloquent is a little bit different than usual and I created a Search method on my model. Which basically perform this:
public function scopeSearch($query, $field, $value)
{
if(is_numeric($value))
return $query->where($field, '=', "$value");
else
return $query;
}
So, I changed my original code to this and my Role is an integer:
public function postSearch(Request $request)
{
$this->authorize('search', User::class);
$query = User::select('name', 'email')
->where('name', 'LIKE', "%$request->name%")
->where('email', 'LIKE', "%$request->email%")
->search('role', "$request->role");
return $this->displayResult($query, $request);
}
Now works fine!

Related

Laravel : How to get all the rows from a table except the first one?

I want to select all the users in my table "User" except the first One cause its the admin,
im using this function index in my controller but it doesn't work .
public function index()
{
// this '!=' for handling the 1 row
$user = User::where('id', '!=', auth()->id())->get();
return view('admin.payroll',compact('user'))->with(['employees' => User::all()]);
}
Better to used here whereNotIn Method
Note: first you find the admin role users and create a static array
$exceptThisUserIds = [1];
$user = User::whereNotIn('id', $exceptThisUserIds)->get();
It's not a good idea to specify the admin user with just id. A better design would be using some sort of a flag like is_admin as a property of your User model.
Still you can use the following code to get the users who have an id greater than 1:
User::where('id', '>', 1)->get()
For getting data skipping the first one you should use skip() method and follow the code like below
public function index()
{
$user = User::orderBy('id','asc')->skip(1)->get();
return view('admin.payroll',compact('user'))->with(['employees' => User::all()]);
}

laravel load specific relation only for "auth" users

Is there any chance to make laravel only loading specific items when the user is authenticated?
Assuming this code:
public function getCategory(Category $category){
$items = $category->products()->with(['otherRelation', 'favouriteUsers' => function($query){
$query->where('user_id', Auth::id())->select(['product_id', 'user_id']);
}])->withCount([
'views',
])->latest()->paginate(20);
At the moment the favouriteUsers relation would be loaded, either the user is authenticated or not, is there any way I can prevent laravel from executing this query unless the user is authenticated?
You need to have the relation favoriteProducts() in the User::class
That way it will be easier to get
$user = Auth::user();
$favoriteProducts = $user->favoriteProducts()
->with('otherRelation')
->withCount('views')
->whereHas('category', function($categoryQB) use($category) {
$categoryQB->where('id', $category->id);
})
->latest()->paginate(20);

using a foreign key or not?

I have a table named 'Feedbacks' with 5 fields:
('id', 'user_id', 'instruction', 'description', 'fk_eleve')
The admin can create several recordings, here is a screenshot.
Here, I have a recording which is Menier Jeremy it's the student
In my rubric 'Eleves' (english: Student) we can see several recordings:
Here, Menier Jeremy has like email address test#gmail.com.
Menier Jeremy wants to connect...
There are 2 rubrics for now:
- Student Profil and Feedback
the user Menier Jeremy can see his profil
However, when the user wants to see his 'feedback'.
Unfortunately, I get the following error message:
SQLSTATE [42S22]: Column not found:
1054 Field Field 'email' unknown in where (SQL: select count (*) as aggregate fromreturnswhere email= test#gmail.com)
I have a problem with the email ?
public function index(Request $request)
{
$user = $request->user();
$feedbacks = Feedback::query()
->when($user->hasRole('admin') !== true, function (Builder $query) use ($user) {
$query->where('email', $user->email);
})
->when($request->has('search'), function (Builder $query) use ($request) {
$query->join('eleves', 'feedbacks.fk_eleve', '=', 'eleves.id')
->orderBy('eleves.nom', 'asc')
->where('eleves.nom', 'like', '%'.$request->input('search').'%');
})
->paginate(5);
return view('admin.feedbacks.index', compact('feedbacks'))
->with('display_search', $user->hasRole('admin'));
}
Do know you where is the problem?
Thank you a lot
Edit code #Watercayman
When, the user Jeremy Menier is connected, I see several recordings:
I have to retrieve only ID n° 1
public function index(Request $request)
{
$user = $request->user();
$feedbacks = Feedback::query()
->when($user->hasRole('admin') !== true, function (Builder $query) use ($user) {
\Auth::user()->load('feedbacks');
$feedbacksForThisUser = \Auth::user()->feedbacks;
})
->when($request->has('search'), function (Builder $query) use ($request) {
$query->join('eleves', 'feedbacks.fk_eleve', '=', 'eleves.id')->orderBy('eleves.nom', 'asc')->where('eleves.nom','like','%'.$request->input('search').'%');
})
->paginate(5);
return view('admin.feedbacks.index', compact('feedbacks'))
->with('display_search', $user->hasRole('admin'));
}
My model User:
public function feedbacks()
{
return $this->hasMany('App\Feedback', 'user_id', 'id');
}
My model Feedback
public function user()
{
return $this->belongsTo('App\User', 'id', 'user_id');
}
Sure, you can get the email for the users that have provided feedback using the FK. But I don't think you need all of the query you are using. I think you can make this much simpler. Because the user has a relation with the Feedback model (I assume through an Eleve model), you can simply eager load the relationship using the name search if you wish:
// Note I have removed the query() method you had in your original query
$feedbacks = Feedback::with('eleves', function ($query) use($request)
$query->orderBy('eleves.nom','asc')->where('eleves.nom','like','%'.$request->input('search').'%');
})->paginate(5);
To simplify the explanation I've removed the where from the query. To demonstrate I'll just use an if check before the query - you can make this more complex if you wish, but it is a little easier to see this way.
Something like:
if($request->has('search'){ do the query above }
else { $feedbacks = Feedback::with('eleves')->paginate(5); }
Then, because you have pulled the relations, you can then just get the email from any eleves that have been loaded:
$feedback->first()->eleve->email
So if you use the $feedbacks in a loop in your blade file:
#foreach($feedbacks as $feedback)
{{ $feedback->eleve->email }}
#endforeach
OK, I think because of the edit, the question is pretty different than what was first asked, so I'll add another answer rather than try to convey this in the comments.
I think you are asking that when the user tries to see his feedbacks, you are getting the SQL error. If the user is selecting the feedback option, it may only be the single user that is looking for his own feedbacks. I suggest you change your query to the reverse (pull the feedbacks from the user instead of pulling all the feedbacks and then getting user to match):
\Auth::user()->load('feedbacks');
$feedbacksForThisUser = \Auth::user()->feedbacks;
Or if you want to get another user's feedbacks (and the user has permission to see them), just query on that user:
$user = User::with('feedbacks')->find($idOfUserYouWantToSee);
$feedbacks = $user->feedbacks;
Same thing goes for if you want to search for feedbacks from a user with a specific name:
$user = User::with('feedbacks')->where('nom','like','%'.$request->input('search').'%')->first();
$feedbacksForSearchedUser = $user->feedbacks;
The key is to forget about the emails - the relationship will link all feedbacks to the user. You don't need to join, or query on the email. You always have it on the $user object. (i.e. $user->email will always work).
If you want to see ALL feedbacks from ALL users, this isn't really any harder, just query all users and eager load the feedbacks relationship:
$users = User::with("feedbacks")->get();
Then in your blade:
#foreach($users as $user)
// If you want to display only a special set of users to the Auth::User(), then do an if-check here
#if(\Auth::user()->hasPerm("someperm") && $user->isInSomeCategory)
#foreach($user->feedbacks as $feedback)
{{ $feedback->text // or whatever the field you want to show }}
#endforeach
#endif // The if check is totally optional in this - just giving you an example
#endforeach
EDIT: to show a simple index()
public function index(Request $request)
{
if(!\Auth::user()->hasRole('admin')) {
\Auth::user()->load('feedbacks');
$feedback = \Auth::user()->feedbacks;
}
elseif($request->has('search')) {
$user = User::with('feedbacks')->where('nom','like','%'.$request->input('search').'%')->first();
$feedbacks = $user->feedbacks;
}
return view('admin.feedbacks.index', compact('feedbacks'));
}
Note: to make this as simple as possible, I also removed:
->with('display_search', $user->hasRole('admin'));
You can check this on the Blade page because \Auth::user() is already loaded automatically.

laravel eloquent sort by relationship

I have 3 models
User
Channel
Reply
model relations
user have belongsToMany('App\Channel');
channel have hasMany('App\Reply', 'channel_id', 'id')->oldest();
let's say i have 2 channels
- channel-1
- channel-2
channel-2 has latest replies than channel-1
now, i want to order the user's channel by its channel's current reply.
just like some chat application.
how can i order the user's channel just like this?
channel-2
channel-1
i already tried some codes. but nothing happen
// User Model
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved')
->with(['replies'])
->orderBy('replies.created_at'); // error
}
// also
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved')
->with(['replies' => function($qry) {
$qry->latest();
}]);
}
// but i did not get the expected result
EDIT
also, i tried this. yes i did get the expected result but it would not load all channel if there's no reply.
public function channels()
{
return $this->belongsToMany('App\Channel')
->withPivot('is_approved')
->join('replies', 'replies.channel_id', '=', 'channels.id')
->groupBy('replies.channel_id')
->orderBy('replies.created_at', 'ASC');
}
EDIT:
According to my knowledge, eager load with method run 2nd query. That's why you can't achieve what you want with eager loading with method.
I think use join method in combination with relationship method is the solution. The following solution is fully tested and work well.
// In User Model
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved');
}
public function sortedChannels($orderBy)
{
return $this->channels()
->join('replies', 'replies.channel_id', '=', 'channel.id')
->orderBy('replies.created_at', $orderBy)
->get();
}
Then you can call $user->sortedChannels('desc') to get the list of channels order by replies created_at attribute.
For condition like channels (which may or may not have replies), just use leftJoin method.
public function sortedChannels($orderBy)
{
return $this->channels()
->leftJoin('replies', 'channel.id', '=', 'replies.channel_id')
->orderBy('replies.created_at', $orderBy)
->get();
}
Edit:
If you want to add groupBy method to the query, you have to pay special attention to your orderBy clause. Because in Sql nature, Group By clause run first before Order By clause. See detail this problem at this stackoverflow question.
So if you add groupBy method, you have to use orderByRaw method and should be implemented like the following.
return $this->channels()
->leftJoin('replies', 'channels.id', '=', 'replies.channel_id')
->groupBy(['channels.id'])
->orderByRaw('max(replies.created_at) desc')
->get();
Inside your channel class you need to create this hasOne relation (you channel hasMany replies, but it hasOne latest reply):
public function latestReply()
{
return $this->hasOne(\App\Reply)->latest();
}
You can now get all channels ordered by latest reply like this:
Channel::with('latestReply')->get()->sortByDesc('latestReply.created_at');
To get all channels from the user ordered by latest reply you would need that method:
public function getChannelsOrderdByLatestReply()
{
return $this->channels()->with('latestReply')->get()->sortByDesc('latestReply.created_at');
}
where channels() is given by:
public function channels()
{
return $this->belongsToMany('App\Channel');
}
Firstly, you don't have to specify the name of the pivot table if you follow Laravel's naming convention so your code looks a bit cleaner:
public function channels()
{
return $this->belongsToMany('App\Channel') ...
Secondly, you'd have to call join explicitly to achieve the result in one query:
public function channels()
{
return $this->belongsToMany(Channel::class) // a bit more clean
->withPivot('is_approved')
->leftJoin('replies', 'replies.channel_id', '=', 'channels.id') // channels.id
->groupBy('replies.channel_id')
->orderBy('replies.created_at', 'desc');
}
If you have a hasOne() relationship, you can sort all the records by doing:
$results = Channel::with('reply')
->join('replies', 'channels.replay_id', '=', 'replies.id')
->orderBy('replies.created_at', 'desc')
->paginate(10);
This sorts all the channels records by the newest replies (assuming you have only one reply per channel.) This is not your case, but someone may be looking for something like this (as I was.)

How to get all posts from the User's subscriptions

In my application, I have setup a User model that can have subscribers and subscriptions through a pivot table called subscriptions.
public function subscribers()
{
return $this->belongsToMany('Forum\User', 'subscriptions', 'subscription_id', 'subscriber_id');
}
public function subscriptions()
{
return $this->belongsToMany('Forum\User', 'subscriptions', 'subscriber_id', 'subscription_id');
}
My question is, what relationship should I use to get a list of paginated Post models (belong to a User) from the User's subscriptions?
You can use the whereHas method to filter based on relationships. Assuming your Post model has a user relationship defined, your code would look something like:
// target user
$user = \App\User::first();
$userId = $user->id;
// get all of the posts that belong to users that have your target user as a subscriber
\App\Post::whereHas('user.subscribers', function ($query) use ($userId) {
return $query->where('id', $userId);
})->paginate(10);
You can read more about querying relationship existence in the documentation.
You can do something like this
\App\Post::with(['subscriptions' => function ($query) {
$query->where('date', 'like', '%date%');
}])->paginate(15);
Or without any conditions
\App\Post::with('subscriptions')->paginate(15);

Categories