When I want users not to be able to enter an individual resource I can use policies to do the following:
public function view(User $user, Model $object)
{
if($user->groupName != $object->groupName) {
return false;
} else {
return true;
}
}
This has as a result that the Components of your group have the eye icon (see red cirle). Components I do not want the user to see dont have the eye icon.
My desired result is that the should not be seen component is not shown at all. How can I achieve this?
I tried:
public function viewAny(User $user)
{
// $object does not exist here so I cannot use it to filter
if($user->groupName == $object->groupName) {
return true;
} else {
return false;
}
}
You need to update the index query of your resource. see more
public static function indexQuery(NovaRequest $request, $query)
{
return $query->where('groupName', $request->user()->group_name);
}
You should consider updating the relateble query too.
Related
I'm trying to figure out how I can get a nested relationship but without any success.
Modal: Workout
public static function getWorkout($workout_id = null)
{
if ($workout_id) {
return Workout::whereId($workout_id)->with('exercises.sets')->first();
}
return [];
}
public function exercises()
{
return $this->belongsToMany('App\Models\Exercise');
)
Modal: Exercise
public function sets()
{
return $this->hasMany('App\Models\Set');
}
This solution gives me all sets based on "exercise_id". I need to get only the sets within the workout.
If I do this it works, the problem is now how I should get the ID of the workout to pass. I've tried to put the relation in the Workout Model as well but then the response of sets will get outside the exercise array.
public function sets()
{
return $this->hasMany('App\Models\Set')->where('workout_id', 5);
}
Try the following snippet where I specify a condition for the eager load.
public static function getWorkout($workout_id = null)
{
if ($workout_id) {
return Workout::whereId($workout_id)->with(['exercises.sets' => function($query) use($workout_id)
{
$query->where('workout_id', $workout_id);
}])->first();
}
return [];
}
I Have this 3 tables like below :
Tools
Parts
Part_details
it is my the table structure :
Tool -> has many -> parts. part -> has many->part_details.
Tool : id*, name; Parts : id*, name, tool_id; part_details: id, part_id, total;
Question :
Using laravel Model, how can I get Tool with One part that has biggest total on parts_details ??
// Tool Model
public function parts(){
return $this->hasMany(Part::class);
}
// Part Model
public function part(){
return $this->belongsTo(Tool::class);
}
public function part_details(){
return $this->hasMany(PartDetail::class);
}
// PartDetail Model
public function part(){
return $this->belongsTo(Part::class);
}
Now query the Tool model
$tools = Tool::with('parts')->withCount('parts.part_details')->get();
$toolWithMaxCount = $tools->filter(function($tool) use ($tools){
return $tool->parts->max('par_details_count') === $tools->max('parts.part_details_count');
})->first();
You can improve this with adding some raw bindings to optimise it. I think you got the idea.
Tool model
public function parts() {
return $this->hasMany('App\Part');
}
Part Model
public function details() {
return $this->hasMany('App\PartDetail');
}
public function tool() {
return $this->belongsToMany('App\Tool');
}
Detail Model
public function part() {
return $this->belongsToMany('App\Part');
}
Controller
$tools = Tool::with('parts', 'parts.details')
->find($id)
->max('parts.part_details');
Use the the hasManyThrough Relationship to get the all part details related to tool and then you can check the one by one record and get the highest total of the tool part.
// Tool Model
public function partsdetails()
{
return $this->hasManyThrough('App\PartDetail', 'App\Part','tool_id','part_id');
}
In Your controller
$data = Tool::all();
$array = [];
if(isset($data) && !empty($data)) {
foreach ($data as $key => $value) {
$array[$value->id] = Tool::find($value->id)->partsdetails()->sum('total');
}
}
if(is_array($array) && !empty($array)) {
$maxs = array_keys($array, max($array));
print_r($maxs);
}
else{
echo "No Data Available";
}
I'm trying to build an alternative relationship that returns all records instead of only related records. I have tried returning a query builder, but that doesn't work, it must be a relationship. What should I return to make this work?
public function devices()
{
if ($this->admin) {
// return all devices relationship instead
} else {
return $this->belongsToMany('Device', 'permissions');
}
}
Fiddle: https://implode.io/XXLGG8
Edit: I'd like to continue building the query in most cases, not just get the devices.
The devices() function in your model is expected to return a relation, you shouldn't add the if statement there. Make your devices() function like this:
public function devices()
{
return $this->belongsToMany('Device', 'permissions');
}
In your User model add a new function:
public function getDevices() {
if($this->admin === true) {
return Device::all();
}
return $this->devices();
}
Now you can do:
$admin->getDevices(); // will return all devices
$user->getDevices(); // will return only relations
I actually went a slightly different way and used a scope:
protected function scopeHasAccess($query, User $user)
{
if ($user->admin) {
return $query;
}
return $query->join('permissions', 'permissions.device_id', "devices.id")
->where('permissions.user_id', $user->user_id);
}
Add devices accessor method to the User model and implement your logic there.
public function getDevicesAttribute() {
if ($this->admin) {
return Device::all();
}
return $this->getRelationValue('devices');
}
See updated "fiddle".
I would like to know how to implement a check for a field inside voters of an entity.
I have for example my entity Post where I want that a user not admin can't edit title field. Only admin can edit this field.
So I have created my voters but I don't know how to create this check because inside $post there is the old post entity and I don't know how to implement the check for title field
This is my easy voters file
class PostVoter extends Voter
{
const VIEW = 'view';
const EDIT = 'edit';
private $decisionManager;
public function __construct(AccessDecisionManagerInterface $decisionManager)
{
$this->decisionManager = $decisionManager;
}
protected function supports($attribute, $subject)
{
if (!in_array($attribute, array(self::VIEW, self::EDIT))) {
return false;
}
if (!$subject instanceof Post) {
return false;
}
return true;
}
protected function voteOnAttribute(
$attribute,
$subject,
TokenInterface $token
) {
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
if ($this->decisionManager->decide($token, array('ROLE_SUPER_ADMIN'))) {
return true;
}
/** #var Post $post */
$post = $subject;
switch ($attribute) {
case self::VIEW:
return $this->canView($post, $user);
case self::EDIT:
return $this->canEdit($post, $user);
}
throw new \LogicException('This code should not be reached!');
}
private function canView(Post $post, User $user)
{
if ($this->canEdit($post, $user)) {
return true;
}
return true;
}
private function canEdit(Post $post, User $user)
{
return $user === $post->getUser();
}
}
I would like to implement inside canEdit a check for the title field.
I have tried to print $post but there is only old value not some information for new value.
Couple of possible approaches.
The one I would use is to add a 'edit_title' permission to the voter then adjust my form to make the title read only if the edit_title permission was denied. This not only eliminates the need to check for a changed title but also makes things a bit friendlier for the users. One might imagine them being a bit frustrated with a form that allows them to change the title but then the app rejects the change.
If you really wanted to detect a title change then you could adjust the setTitle method in your post entity. Something like:
class Post {
private $titleWasChanged = false;
public function setTitle($title) {
if ($title !== $this->title) $this->titleWasChanged = true;
$this->title = $title;
And then of course check $titleWasChanged from the voter.
If you really wanted to go all out, the Doctrine entity manager actually has some change checking capability. You could probably access it via the voter but that would probably be overkill. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/change-tracking-policies.html
I am trying to filter my Eloquent model collections based on if a user has access to the model or not.
My current method works, but it is really slow so I am wondering if there is a more performant way to do it?
I have a userHasAccess() method on every model in the collection.
It uses Laravel's ACL Features to determine if the user has access to the model:
public function userHasAccess()
{
if (Auth::user()->can('show', $this)) {
return true;
}
return false;
}
I then override the newCollection() method on the model:
public function newCollection(array $models = Array())
{
$collection = new Collection($models);
$collection = $collection->filter(function($model)
{
if($model->userHasAccess())
return true;
});
return $collection;
}
The policy method looks like this:
public function show(User $user, Quote $quote)
{
if(!$quote->customer)
return false;
if(($user->id === $quote->user_id))
return true;
if($user->hasRole(['super-admin','admin']))
return true;
return false;
}
Is there a better way to do this? Especially in terms of performance?
You could add the logic to the query and speed it up dramatically
$query = User::query();
if(!Auth::user()->hasRole(['super-admin','admin'])){
$query->where('user_id','=',Auth::id);
}
$data = $query->get();
You could do this on a wider scale using a scope
class User extends Model
{
public function scopeLimitByUser($query)
{
if(!Auth::user()->hasRole(['super-admin','admin'])){
$query->where('user_id','=',Auth::id);
}
}
}
Then for the quote customer you can add a where to the query
$query->whereNotNull('customer_id');