How can I fix Laravel comments policy not working? - php

I am using inertia JS.
I created a forum where you can manage your posts and comments (CRUD).
Normally, the one who can modify or delete his post or comment is the one who created it and the administrator.
I was able to set up a policy for the post but for the comment it does not work. I need your help to fix this.
This is my show function for post and comments
public function show(Post $post, Comment $comment)
{
usleep(500000);
$post->incrementReadCount();
$updateableCommentIds = $post->comments
->map(function ($comment) {
if (Auth::user()->can('update', $comment)) {
return $comment->id;
}
})
->filter();
return Inertia::render('Frontend/Forum/Helpers/PostDetails', [
'post' => PostResource::make(
$post->load('user')->loadCount('comments')
),
'comments' => CommentResource::collection(
Comment::where('post_id', $post->id)
->with('user')
->paginate(10)
->withQueryString()
),
'categories' => Category::all(),
'can' => [
'edit' => Auth::check() && Auth::user()->can('edit', $post),
'commentEdit' => $updateableCommentIds
]
]);
}
This's my comment policy
class CommentPolicy
{
use HandlesAuthorization;
public function update(User $user, Comment $comment): bool
{
return $user->is_admin || $user->id === (int) $comment->user_id;
}
}
This's my vue file
<div
v-if="can.commentEdit.includes(comment.id)"
>
//show me this if im the auther of this comment
</div>
I already tried but it doesn't work either
public function show(Post $post)
{
$canUpdateComments = $post->comments->every(function ($comment) {
return Auth::user()->can('update', $comment);
});
// Return the view with the ability to update the comments
return view('posts.show', compact('post', 'canUpdateComments'));
}

I just noticed that I had a commentResource and just with that I found the solution instead of checking each time on the post not directly on the comment...
class CommentResource extends JsonResource
{
public function toArray($request)
{
return [
...
'can' => [
'edit' => Auth::user()->can('update', $this->resource)
]
];
}
}

Related

Yii Missing attribute when using join() + one()

I don't know if this is correct behavior of Yii Active Record, consider I have this code
$post = Post::find()
->alias('p')
->select(['p.*', 'COUNT(c.id) AS comment_count'])
->join('LEFT JOIN', 'comments c', 'p.id = c.post_id')
->groupBy('p.id')
->one();
I cannot access $post->comment_count, but when I use ->asArray()->one, I can access $post['comment_count'], is it possible to return as Post model while having access to comment_count? As this can be used for validation, example
// $post from code above
if ($post->comment_count != 0) {
throw new UnprocessableEntityHttpException('Cannot delete post with comment(s)');
}
return $post->delete();
You need to add $comment_count inside the Post model, for example:
class Post {
public $comment_count;
.....
public function attributeLabels()
{
return [
'comment_count' => 'Total Comment',
.....
]
}
But if you are satisfied with asArray() as what you mentioned earlier, I think that is enough because it's pretty faster.

Axios GET with params being ignored

Newbie to all the tech I'm using here.
Trying to query a table using Axios in Laravel with Vue.js. Here's what I got:
from my component's <script>
this.axios
.get('http://localhost:8000/api/tasks', {params: {vid: this.properties[0].vid}})
.then(response => {
this.tasks = response.data;
console.log(response)
})
Regardless of what vid is in the params, the response contains ALL of the table's rows.
I do know that this may have something to do with api.php, and the Controller associated with the request. I'll post those to be verbose.
routes/api.php
Route::middleware('api')->group(function () {
Route::resource('vehicles', VehicleController::class);
Route::resource('tasks', TaskController::class);
Route::get('tasks?={vid}', [TaskControler::class, 'search']);
});
Controllers/TaskController.php
class TaskController extends Controller
{
//
public function index() {
$tasks = Task::all()->toArray();
return $tasks;
}
public function store(Request $request){
$task = new Task([
'make' => $request->input('make'),
'model' => $request->input('model'),
'year' => $request->input('year'),
'mileage' => $request->input('mileage'),
'type' => $request->input('type'),
'VIN' => $request->input('VIN')
]);
$task->save();
}
public function show($tid){
$task = Task::find($tid);
return response()->json($task);
}
public function update($tid, Request $request){
$task = Task::find($tid);
$task->update($request->all());
return response()->json('Task Updated!');
}
public function destroy($tid){
$task = Task::find($tid);
$task->delete();
return response()->json('Task Deleted!');
}
}
I tried for a while to mess around with api and the controller, but to no avail. Most of the questions asked here give "just use params" as an answer, but despite my best efforts I don't seem to be able to get away with "just" params (willing to be proven wrong.)

What is the best way to manage home page banners in Laravel?

This is my banners structure in home page :
As you can see I have 4 section for banners - small banners | medium banners | news banners | large banners
And I have one model called Banner , 4 controllers to manage this banners and 4 tables to save data.
This is Banner model :
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Banner extends Model
{
protected $fillable = [
'title', 'image', 'url', 'image_title', 'image_alt'
];
}
And Controllers :
SmallController :
class SmallController extends Controller
{
public function small_list()
{
$smallBanners = DB::table('small_banner')->get();
return view('admin.banners.small.list', compact('smallBanners'));
}
public function small_create()
{
return view('admin.banners.small.add');
}
public function small_store(Request $request)
{
$data = $request->validate([
'title' => 'required',
'url' => 'required',
'image' => 'required',
'image_title' => 'max:255',
'image_alt' => 'max:255'
]);
DB::table('small_banner')->insert($data);
return redirect(route('admin.banners.small.index'));
}
public function small_edit($id)
{
$small = DB::table('small_banner')->where('id', $id)->first();
return view('admin.banners.small.edit', compact('small'));
}
public function small_update(Request $request, $id)
{
$small = DB::table('small_banner')->where('id', $id)->first();
if ($request->has('image')) {
if (file_exists($small->image)) {
unlink($small->image);
}
DB::table('small_banner')->where('id', $id)->update([
'image' => $request['image']
]);
}
DB::table('small_banner')->where('id', $id)->update([
'title' => $request['title'],
'url' => $request['url'],
'image_title' => $request['image_title'],
'image_alt' => $request['image_alt']
]);
return redirect(route('admin.banners.small.index'));
}
public function small_delete($id)
{
$small = DB::table('small_banner')->where('id', $id)->first();
DB::table('small_banner')->where('id', $id)->delete();
if (file_exists($small->image)) {
unlink($small->image);
}
return redirect(route('admin.banners.small.index'));
}
}
Other Controllers are like SmallController
And this is how I show this banners :
#foreach($smallBanners as $small)
<div class="col-6 col-lg-3">
<div class="widget-banner card">
<a href="{{ $small->url }}" target="_blank" rel="noopener">
<img class="img-fluid w-100" loading="lazy"
src="{{ $small->image }}" title="{{ $small->title }}"
alt="{{ $small->image_alt }}" width="350" height="200">
</a>
</div>
</div>
#endforeach
Other views like small banner.
But in this case, for example in small banners, if we upload 5 images instead 4 images, the structure will be messed up.
What is the best way to manage this banners and optimize codes ?
let's back to the concept, starting from reducing table usage, or you can stay with your concept
lets's change the structure into below
table : banners
columns :
$table->increments('id');
$table->string('title');
$table->string('image');
$table->string('url');
$table->string('image_title')->nullable(); //guessing from validator that it can be null
$table->string('image_alt')->nullable();
//extra columns
$table->enums('banner_type', ['small', 'medium', 'large', 'news']);
//or
$table->string('banner_type');
$table->boolean('isActive')->default(0);
you have model, but not using it
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Banner extends Model
{
protected $table = 'banners'; //add this line to define table name, make sure you have set the database config in .env
protected $fillable = [
'title', 'image', 'url', 'image_title', 'image_alt', 'banner_type', 'isActive'
];
}
now reducing the controller used to manage banners into just 1 Controller
use Banner;
class BannerController extends Controller
{
public function index()
{
$banners = Banner::get();
return view('admin.banners.index', compact('banners'));
}
public function create()
{
return view('admin.banners.create');
}
public function store_count($request, $type)
{
//using array limit
return Banner::where('banner_type', $type)
->where('isActive', 1)->count() < $this->limits[$type] && $request->isActive == 1;
}
public function update_count($banner, $type)
{
return Banner::whereNotIn('id', [$banner->id])
->where('isActive', 1)
->where('type', $banner->banner_type)->count() < $this->limits[$type] && $banner->isActive == 1;
}
public function store(Request $request)
{
//validating form data
$data = $request->validate([
'title' => "required",
'url' => "required",
'image' => "required",
'image_title' => "max:255",
'image_alt' => "max:255",
'banner_type' => "required|in:small,medium,large,news",
'isActive' => "nullable|in:0,1" //active or not
]);
//validating images active count
if (!$this->store_count($request, $request->banner_type)) {
return redirect()->back()->withInput($request->all())
->withErrors(['isActive' => ' نمیتوان بیشتر از ' . $this->limits[$request['banner_type']] . ' عکس برای این بنر آپلود کرد! ']);
}
Banner::create($data);
return redirect(route('admin.banners.index'));
}
public function show($id)
{
$banner = Banner::findOrFail($id);
return view('admin.banners.edit', compact('banner'));
}
public function update(Request $request, $id)
{
$banner = Banner::findOrFail($id);
//validate update form data here
//your validation
//validating images active count
if(!$this->update_count($banner, $request->banner_type)){
return redirect()->back()
->withInput($request->all())
->withErrors(['isActive' => 'There cant be more than '.$this->limits[$request['banner_type']].' images active');
}
$banner = $banner->fill([
'title' => $request['title'],
'url' => $request['url'],
'image_title' => $request['image_title'],
'image_alt' => $request['image_alt'],
'banner_type' => $request['banner_type'],
'isActive' => $request['isActive'] ?? 0
]);
if ($request->has('image')) {
if (file_exists($banner->image)) {
unlink($banner->image);
}
$banner->image = $request['image'];
}
$banner->update();
return redirect(route('admin.banners.index'));
}
public function delete($id)
{
$banner = Banner::findOrFail($id);
if (file_exists($banner->image)) {
unlink($banner->image);
}
$banner->delete();
return redirect(route('admin.banners.index'));
}
}
now we setup code to choose which images are active, you can use ajax method or use controller above
public function set_active($id)
{
$banner = Banner::findOrFail($id);
$this->validate_count((new Request([])), $banner->banner_type);
$banner->update(['isActive' => 1]);
return redirect(route('admin.banners.index'));
}
//you can use array if want to set different limit of banner type, put it as public variable inside controller class
public $limits = [
'small' => 4,
'medium' => 4,
'large' => 4,
'news' => 4
];
load the data resource into view
public class home()
{
$small = Banner::where('banner_type', 'small')
->where('isActive', 1)->get();
$medium = Banner::where('banner_type', 'medium')
->where('isActive', 1)->get();
$large = Banner::where('banner_type', 'large')
->where('isActive', 1)->get();
$news = Banner::where('banner_type', 'news')
->where('isActive', 1)->get();
return view('home', compact('small', 'medium', 'large', 'news'));
}

Querying MorphToMany Relation Laravel

The thing I want to get from the database is that get all the posts with the data which will identify whether the post is liked by the auth()->user() or not. Most probably via count.
App\Post
public function likes()
{
return $this->morphToMany('App\User', 'likeable');
}
App\User
public function likePosts()
{
return $this->morphedByMany('App\Post', 'likeable')->withTimestamps();
}
Likeables Table
Likeables table has ('user_id', 'likeable_id', 'likeable_type')
I tried using orWhereHas
$posts = Post::with( ['user', 'tags', 'category'])->orwhereHas('likes', function($q) {
$q->where('user_id', auth()->id());
})->latest()->withoutTrashed()->paginate(10);
But with about query I am only getting those posts which the user has liked. I want to get all posts and a check whether the post is liked by the user or not
I came across whereHasMorph but it was only for morphTo and not for morphToMany.
#m____ilk I was able to solve this but creating a mutator:
public function isLiked()
{
return $this->likes()->where('user_id', auth()->id())->count() > 0;
}
I ran a loop on the posts and attached a custom attribute to a single post based on the mutator.
$posts = Post::with( ['user', 'tags', 'category', 'post.user', 'post.tags', 'post.category'])->latest()->withoutTrashed()->paginate(10);
foreach ($posts as $post) {
// Mutator Condition
if ($post->is_liked) {
// Custom Attribute
$post->isLiked = 1;
} else {
$post->isLiked = 0;
}
}
return $posts;
In laravel 9 I did something like:
$posts = Post::with( ['user', 'tags', 'category', 'post.user', 'post.tags', 'post.category'])
->withCount([
'likes',
'likes as is_liked' => function($q) {
$q->where('user_id', auth()->id());
}
])->latest()->withoutTrashed()->paginate(10)

Category isn't getting related video blogs?

I am trying to get category related video blogs by below code but i get nothing in var_dump? I want to get category related videos:
$category = VideoBlogCategoryModel::findFirst(1); // This returns category successfully and there are many video blogs having this category linked
var_dump($category->getVideoBlogs());exit;
VideoBlogModel.php
public function initialize(){
// Run base initialize code
parent::initialize();
// Configure Relation with VideoBlogCategoryModel
$this->belongsTo('category_id', VideoBlogCategoryModel::class, 'id', array(
'alias' => 'videoCategory',
'foreignKey' => true
));
}
public function getVideoCategory(){
return $this->videoCategory;
}
public function setVideoCategory($videoCategory){
$this->videoCategory = $videoCategory;
}
VideoBlogCategoryModel.php
public function initialize(){
// Run base initialize code
parent::initialize();
// Configure relation with VideoBlogModel
$this->hasMany('id', VideoBlogModel::class, 'category_id', array(
'alias' => 'videoBlogs',
'foreignKey' => true,
'cxAction' => static::ACTION_CASCADE_DELETE
));
}
public function getVideoBlogs(){
return $this->videoBlogs;
}
public function setVideoBlogs($videoBlogs){
$this->videoBlogs = $videoBlogs;
}
Let me know if anything else is required, I will share it.
In VideoBlogCategoryModel.php change
public function getVideoBlogs() {
return $this->videoBlogs;
}
to
public function getVideoBlogs() {
return $this->getRelated('videoBlogs');
}
Then try accessing it like:
$category = VideoBlogCategoryModel::findFirst(1);
$videos = $category->getVideoBlogs();
foreach( $videos as $video ) {
// access data here
var_dump($video->anyProperty()); // e.g $video->getId()
}
can you try it
$category = VideoBlogCategoryModel::findFirst(1);
$videos = $category->getVideoBlogs();
var_dump($videos->count());
var_dump($videos->toArray());
exit;
I think use var_dump for a Phalcon Collection Object is not a good idea, you can convert it to Array and Var_dump
Hope it can help you
Or try:
$categories = VideoBlogCategoryModel::findById($id);

Categories