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

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'));
}

Related

How can I fix Laravel comments policy not working?

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)
]
];
}
}

Display multiple categories in product page (many to many) in laravel/vue

Please help, I am trying to display multiple categories on the product page, but I get the wrong categories. There are many to many relationships. Can not find the answer on google.
Categories table
Records table
category_record table
Product page (Vue), yellow-marked wrong category displayed
Folder tree and API routes
Categories.php
class Categories extends Model
{
public function records() {
return $this->belongsToMany(Record::class, 'category_record', 'record_id', 'categories_id')->withTimestamps();
}
}
Record.php
class Record extends Model
{
protected $fillable = [
'name', 'price', 'description', 'units', 'image', 'size', 'color'
];
public function categories() {
return $this->belongsToMany(Categories::class, 'category_record', 'record_id', 'categories_id')->withTimestamps();
}
}
RecordController.php
class RecordController extends Controller
{
public function index() {
return RecordIndexResource::collection(
Record::all()
);
}
public function show($id) {
return new RecordShowResource(Record::findOrFail($id));
}
}
RecordShowResource.php
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'description' => $this->description,
'price' => $this->price,
'units' => $this->units,
'size' => $this->size,
'color' => $this->color,
'image' => $this->image,
];
}
Record.vue to display data:
<template>
<div class="review-page">
<div class="record-wrapper" v-if="!loading">
<div class="record-text">
<img :src="'/storage/' + record.image" :alt="record.title" />
<div class="record-title">{{ record.title }}</div>
<hr />
<div v-for="(category, index) in categories" :key="index">
<p>Categories: {{ category.name }}</p>
</div>
<br />
<p>Product Details</p>
<div class="record-description">{{ record.description }}</div>
<div class="record-details-price">€{{ record.price }}</div>
</div>
<record-options v-bind="record" />
</div>
<div class="loading" v-else></div>
</div>
</template>
<script>
import RecordOptions from "./RecordOptions.vue";
export default {
components: {
RecordOptions,
},
data() {
return {
record: null,
categories: [],
loading: false,
};
},
async created() {
this.loading = true;
await axios.get(`/api/records/${this.$route.params.id}`).then((response) => {
this.record = response.data.data;
this.loading = false;
});
axios
.get(`/api/categories/${this.$route.params.id}`)
.then((response) => {
this.categories = response.data;
})
.catch((error) => {
console.log(error);
});
},
};
</script>
please help me to find out the way.

Locate function call in laravel 5.8

Hello I'm new to laravel but not php. I was tasked to optimized some backend query and needed some help on locating where the model function "getPackagesSoldAttribute" is being called here. I don't see in the controller that this function is being called like ProEvent::getPackagesSoldAttribute for example.
So my view is showing the return of the function. Any idea how did laravel called this function? I also did check PHP DebugBar and I saw the query the getPackagesSoldAttribute function used.
I also created a script that would locate the string "getPackagesSoldAttribute" but could only find one and that is in the model.
Route::get('{organiser_id}/pro_events/{filter?}', [
'as' => 'showOrganiserProEvents',
'uses' => 'OrganiserProEventsController#showOrganiserProEvents',
]);
This is the controller function called by route.
public function showOrganiserProEvents(Request $request, $organiser_id, $filter = null, $listView = null)
{
$add_on_check = \App\Models\OrganisationAddon::where('org_id', $organiser_id)->first();
$edit_create_enable = "";
$delete_enable = "";
if( !empty($add_on_check) ){
$add_ons = json_decode($add_on_check->add_ons);
$edit_create_enable = !empty($add_ons->enable_create_edit_event)?"1":"";
$delete_enable = !empty($add_ons->enable_delete_event)?"1":"";
}
$organiser = Organiser::find($organiser_id);
$allowed_sorts = ['created_at', 'event_date', 'title'];
$searchQuery = $request->get('q');
$sort_by = (in_array($request->get('sort_by'), $allowed_sorts) ? $request->get('sort_by') : 'event_date');
$eventListView = empty($request->get('view_list_by')) ? 'grid' : $request->get('view_list_by');
if($listView){
$eventListView = $listView;
}
if($filter == null){
$filter = "live";
}
if( $searchQuery ){
$the_event = ProEvent::where('title', 'like', '%' . $searchQuery . '%');
if($sort_by == 'event_date'){
$the_event->orderBy($sort_by, 'asc');
} else {
$the_event->orderBy($sort_by, 'desc');
}
$the_event->where('organiser_id', '=', $organiser_id);
if( $filter == "templates"){
$the_event->where(function($query){
$query->where('event_status', 'template');
$query->orWhere('is_master', 1);
});
}else if( $filter == "live" ){
$the_event->where('event_status', "live");
}else if( $filter == "past" ){
$the_event->where(function($query){
$query->where('event_status', 'past');
$query->orWhere('event_date', '<', Carbon::now());
});
}else if( $filter == "testing" ){
$the_event->where(function($query){
$query->where('event_status', 'testing');
$query->orWhere('event_status', 'pos_testing');
});
}else if( $filter == "abandoned" ){
$the_event->where('event_status', "abandoned");
}
$events = $the_event->get();
}else{
$the_event = ProEvent::where('organiser_id', '=', $organiser_id);
if($sort_by == 'event_date'){
$the_event->orderBy($sort_by, 'asc');
} else {
$the_event->orderBy($sort_by, 'desc');
}
if( $filter == "templates"){
$the_event->where(function($query){
$query->where('event_status', 'template');
$query->orWhere('is_master', 1);
});
}else if( $filter == "live" ){
$the_event->where('event_status', "live");
}else if( $filter == "past" ){
$the_event->where(function($query){
$query->where('event_status', 'past');
$query->orWhere('event_date', '<', Carbon::now());
});
}else if( $filter == "testing" ){
$the_event->where(function($query){
$query->where('event_status', 'testing');
$query->orWhere('event_status', 'pos_testing');
});
}else if( $filter == "abandoned" ){
$the_event->where('event_status', "abandoned");
}
$events = $the_event->get();
}
$data = [
'event_types' => $organiser->pro_event_types,
'events' => $events,
'organiser' => $organiser,
'search' => [
'q' => $searchQuery ? $searchQuery : '',
'sort_by' => $request->get('sort_by') ? $request->get('sort_by') : '',
'showPast' => $request->get('past'),
],
'edit_create_enable' => $edit_create_enable,
'delete_enable' => $delete_enable,
'filter' => $filter,
'eventListView' => $eventListView
];
return view('ManageEmsPro.ProEvents', $data);
}
This is the model
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Session;
use Str;
use URL;
use Illuminate\Http\UploadedFile;
use File;
use Image;
use Log;
use Storage;
class ProEvent extends MyBaseModel
{
use SoftDeletes;
protected $table = 'pro_events';
protected $appends = array('packages_sold', 'bundles_sold');
/**
* The validation rules.
*
* #var array $rules
*/
protected $rules = [
'information_link' => ['regex:/^(https?:\/\/)([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/'],
'redirect_link_url' => ['regex:/^(https?:\/\/)([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/'],
'web_sales_start_date' => ['required'],
'sales_start_date' => ['required'],
'open_time' => ['required'],
'close_time' => ['required'],
'title' => ['required'],
'description' => ['required'],
'event_date' => ['required'],
'organiser_name' => ['required_without:organiser_id'],
'event_image' => ['mimes:jpeg,jpg,png', 'max:3000'],
'custom_url' => ['regex:/(^[\w\d_-]+$)/u'],
'payment_percentage' => ['numeric', 'between:0,99.99', 'min:1'],
'event_logo' => ['mimes:jpeg,jpg,png', 'max:1000'],
'organiser_event_logo' => ['mimes:jpeg,jpg,png', 'max:1000'],
];
/**
* The validation error messages.
*
* #var array $messages
*/
protected $messages = [
'title.required' => 'You must at least give a title for your event.',
'organiser_name.required_without' => 'Please create an organiser or select an existing organiser.',
'event_image.mimes' => 'Please ensure you are uploading an image (JPG, PNG, JPEG)',
'event_image.max' => 'Please ensure the image is not larger then 3MB',
'custom_url.regex' => 'Allowed special characters are only "_" and "-"',
'event_logo.mimes' => 'Please ensure you are uploading an image (JPG, PNG, JPEG)',
'event_logo.max' => 'Please ensure the image is not larger then 1MB',
];
...
public function getPackagesSoldAttribute(){
$valid_packages = PackageInventoryMap::where("pro_event_id", $this->id)->pluck('package_id');
$valid_orders = ProOrders::whereNotIn("order_status_id", [2,4])->where("organiser_id", $this->organiser_id)->pluck('id');
$pro_order_items = ProOrderItem::whereIn('pro_order_id', $valid_orders)
->where("pro_event_id", $this->id)
->whereIn('package_id', $valid_packages)
->whereNull("bundle_ids")
->sum('quantity');
return (integer)$pro_order_items;
}
...
This is part of the view.
<div class="row">
#if($events->count())
#if($eventListView == "grid")
<div class="col-sm-12 grid-search">
<div class="col-sm-offset-6 col-sm-6">
<div class="pull-right lh-2">
Search: <input class="form-control pull-right input-sm grid-filter">
</div>
</div>
</div>
#foreach($events as $key => $event)
<div class="col-md-6 col-sm-6 col-xs-12 data-event-block" data-event-block="{{ $key+1 }}" style="display: {{ $key <= 9 ? 'block' : 'none' }}">
#include('ManageEmsPro.Partials.EventPanel')
</div>
#endforeach
#elseif($eventListView == "table")
<div class="col-md-12">
#include('ManageEmsPro.Partials.EventTable')
</div>
#endif
#else
#if($search['q'])
#include('Shared.Partials.NoSearchResults')
#else
#include('ManageEmsPro.Partials.EventsBlankSlate')
#endif
#endif
</div>
See Eloquent: Mutators. getPackagesSoldAttribute is an "Accessor" method. Basically, you wont see getPackagesSoldAttribute called directly, instead, when the code calls $model->packages_sold, in the background, laravel will actually call $model->getPackagesSoldAttribute dynamically and return its response as that value.
For example, if you wanted to debug or test this method, you might do something like:
$event = ProEvent::find($someId);
$packagesSold = $event->packages_sold; // this line will call the `getPackagesSoldAttribute` and set `$packagesSold` to the value it returns.
Also of note:
The model here also defines an appends property with packages_sold
protected $appends = array('packages_sold', 'bundles_sold');
This will cause the packages_sold property to be automatically filled by the method in question any time the model is represented as an array or serialized. see also
Appending Values To JSON

how to show project name in limited characters in Laravel?

I need show My laravel application project name with limited characters. that means as an example consider some project name contains more than 10 letters, then I need only show 10 characters.
My project name create controller is
public function store(Request $request)
{
$this->validate($request, [
'name' => 'required|min:3',
'notes' => 'required|min:10',
'color' => 'required',
'group' => 'required',
'status' => 'required'
]);
$project = new Project;
$project->project_name = $request->input('name');
$project->project_status = $request->input('status');
$project->group = $request->input('group');
$project->color = $request->input('color');
$project->project_notes = $request->input('notes');
$project->user_id = Auth::user()->id;
$duplicate = Project::where('project_name',$project->project_name)->first();
if($duplicate)
{
return redirect()->route('projects.index')->with('warning','Title already exists');
}
$project->save();
return redirect()->route('projects.index')->with('info','Your Project has been created successfully');
and project name showing blade file is
<div class="row">
#foreach ($projects as $proj)
<div class="col-md-3" style="border:3px solid {!!$proj->color!!};margin-left:5px;margin-bottom: 5px;">
<h2>{!! $proj->project_name !!}</h2>
then how can I show only 10 charactors in My blade file?
You can create a public function in Project model to do this:
public function getShortName($maxLen = 10)
{
if ($this->project_name === null) {
return '-';
}
if (strlen($this->project_name) <= $maxLen) {
return $this->project_name;
}
return substr($this->project_name, 0, $maxLen);
}
Then you can call it in your Blade file like this:
{{ $proj->getShortName() }}
If you want to set another limit to the maximum length, just pass it as a parameter:
{{ $proj->getShortName(20) }}

Laravel 5.5 multiple image upload

I try to use dropzoneJS in order to upload multiple image for my products and so far I can save images in database, also in images folder but I have problem with getting product id to relate each image to products.
Here is what I have:
Databases
Products (where my products including info will save)
Images (where my images including product id will save screenshot provided )
Models
Product:
public function images()
{
return $this->morphMany(Image::class, 'imageable');
}
Image:
class Image extends Model
{
protected $fillable = ['name'];
public function imageable()
{
return $this->morphTo();
}
public function product()
{
return $this->belongsTo(Product::class);
}
}
Image Schema
public function up()
{
Schema::create('images', function (Blueprint $table) {
$table->increments('id');
$table->integer('imageable_id')->nullable();
$table->string('imageable_type')->nullable();
$table->string('name');
$table->timestamps();
});
}
ImageController
class ImageController extends Controller
{
public function dropzone()
{
return view('dropzone-view');
}
public function dropzoneStore(Request $request)
{
// works
$file = $request->file('file');
$filename = 'product' . '-' . time() . '.' . $file->getClientOriginalExtension();
$filePath = public_path('images/');
$request->file('file')->move($filePath, $filename);
return Image::create([
'name' => $filename,
'imageable_id' => $request->input('imageable_id'),
])->id;
}
}
Product Create (Blade)
// Form
{!! Form::open([ 'route' => [ 'dropzone.store' ], 'files' => true, 'enctype' => 'multipart/form-data', 'class' => 'dropzone mt-20', 'id' => 'my-awesome-dropzone' ]) !!}
<div class="fallback">
<input name="file" type="file" multiple />
</div>
<input type="hidden" name="imageIds[]" value="">
{{Form::close()}}
// Javascript
<script type="text/javascript">
Dropzone.autoDiscover = false;
var myDropzone = new Dropzone("form#my-awesome-dropzone", {
headers: {
"X-CSRF-TOKEN": $("meta[name='csrf-token']").attr("content")
},
acceptedFiles: ".jpeg,.jpg,.png,.gif",
dictDefaultMessage: "Drag an image here to upload, or click to select one",
maxFiles: 15, // Maximum Number of Files
maxFilesize: 8, // MB
addRemoveLinks: true,
});
myDropzone.on("success", function (response) {console.log(response.xhr.response); });
</script>
Any idea?
Code for controller:
class ImageController extends Controller
{
public function dropzone($id)
{
$product = Product::find($id);
return view('dropzone-view')
->withProduct($porduct);
}
public function dropzoneStore(Request $request)
{
// works
$file = $request->file('file');
$filename = 'product' . '-' . time() . '.' . $file->getClientOriginalExtension();
$filePath = public_path('images/');
$request->file('file')->move($filePath, $filename);
return Image::create([
'name' => $filename,
'imageable_id' => $request->input('imageable_id'),
])->id;
}
}
Code on blade view:
{!! Form::open([ 'route' => [ 'dropzone.store' ], 'files' => true, 'enctype' => 'multipart/form-data', 'class' => 'dropzone mt-20', 'id' => 'my-awesome-dropzone' ]) !!}
<div class="fallback">
<input name="imageable_id" type="hidden" value="{{$product->id}}" />
<input name="file" type="file" multiple />
</div>
{{Form::close()}}
Try this hope this should get you going. This is one of many way to make this thing work.
this is a morpic relation and this is how you get morphic relation
$post = App\Post::find(1);
foreach ($post->comments as $comment) {
//
}
read about it here polymorphic-relations
What i normally do is,
After you upload a picture, return the ID of the newly created image (you already do that in ImageController)
On your product page, in the dropzone 'success' callback you can read the the image ID and add it to an hidden array input field
In your controller you have to create the new product, and then after saving it , you can attach the images to the correct product, because now you have the id's of the images + the product instance has been made.

Categories