I have been working on a blogging application in Laravel 8.
I use CKEditor 4 in the form tasked with adding a new article.
I web.php I have added this route:
Route::post('/ckupload', [ArticleController::class, 'ckupload'])->name('dashboard.articles.ckupload');
The form view:
<form method="POST" action="{{ route('dashboard.articles.add') }}" enctype="multipart/form-data" novalidate>
@csrf
<div class="row mb-2">
<label for="title" class="col-md-12">{{ __('Title') }}</label>
<div class="col-md-12 @error('title') has-error @enderror">
<input id="title" type="text" placeholder="Title" class="form-control @error('title') is-invalid @enderror" name="title" value="{{ old('title') }}" autocomplete="title" autofocus>
@error('title')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-2">
<label for="short_description" class="col-md-12">{{ __('Short description') }}</label>
<div class="col-md-12 @error('short_description') has-error @enderror">
<input id="short_description" type="text" placeholder="Short description" class="form-control @error('short_description') is-invalid @enderror" name="short_description" value="{{ old('short_description') }}" autocomplete="short_description" autofocus>
@error('short_description')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-2">
<label for="category" class="col-md-12">{{ __('Category') }}</label>
<div class="col-md-12 @error('category_id') has-error @enderror">
<select name="category_id" id="category" class="form-control @error('category_id') is-invalid @enderror">
<option value="0">Pick a category</option>
@foreach($categories as $category)
<option value="{{ $category->id }}">{{ $category->name }}</option>
@endforeach
</select>
@error('category_id')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-2">
<div class="col-md-12 d-flex align-items-center switch-toggle">
<p class="mb-0 me-3">Featured article?</p>
<input class="mt-1" type="checkbox" id="featured" name="featured" {{ old('featured') ? 'checked' : '' }}>
<label class="px-1" for="featured">{{ __('Toggle') }}</label>
</div>
</div>
<div class="row mb-2">
<label for="image" class="col-md-12">{{ __('Article image') }}</label>
<div class="col-md-12 post-image @error('image') has-error @enderror">
<input type="file" value="{{ old('image') }}" name="image" id="file" class="file-upload-btn">
@error('image')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-2">
<label for="content" class="col-md-12">{{ __('Content') }}</label>
<div class="col-md-12 @error('content') has-error @enderror">
<textarea name="content" id="content" class="form-control @error('content') is-invalid @enderror" placeholder="Content" cols="30" rows="6">{{ old('content') }}</textarea>
@error('content')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-0">
<div class="col-md-12">
<button type="submit" class="w-100 btn btn-primary">
{{ __('Save') }}
</button>
</div>
</div>
</form>
In the controller, I have:
namespace App\Http\Controllers\Dashboard;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
use App\Models\ArticleCategory;
use App\Models\Article;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class ArticleController extends Controller
{
private $rules = [
'category_id' => 'required|exists:article_categories,id',
'title' => 'required|string|max:190',
'short_description' => 'required|string|max:190',
'image' => 'image|mimes:jpeg,png,jpg|max:2048',
'content' => 'required|string'
];
private $messages = [
'category_id.required' => 'Please pick a category for the article',
'title.required' => 'Please provide a title for the article',
'short_description.required' => 'The article needs a short description',
'short_description.max' => 'The short description field is too long',
'content.required' => 'Please add content'
];
public function ckupload(Request $request) {
if ($request->hasFile('upload')) {
$fileName = md5(time()) . Auth::user()->id . '.' . $request->image->extension();
$request->image->move(public_path('images/articles'), $fileName);
$CKEditorFuncNum = $request->input('CKEditorFuncNum');
$url = asset('images/articles' . $fileName);
$response = "<script>window.parent.CKEDITOR.tools.callFunction($CKEditorFuncNum, '$url')</script>";
@header('Content-type: text/html; charset=utf-8');
echo $response;
}
return false;
}
public function save(Request $request) {
// Validate form (with custom messages)
$validator = Validator::make($request->all(), $this->rules, $this->messages);
if ($validator->fails()) {
return redirect()->back()->withErrors($validator->errors())->withInput();
}
$fields = $validator->validated();
// Upload article image
if (isset($request->image)) {
$imageName = md5(time()) . Auth::user()->id . '.' . $request->image->extension();
$request->image->move(public_path('images/articles'), $imageName);
}
// If no image is uploaded, use default.jpg
$fields['image'] = null == $request->image ? 'default.jpg' : $imageName;
// Upload image via CKEDITOR
$this->ckupload($request);
// Turn the 'featured' field value into a tiny integer
$fields['featured'] = $request->get('featured') == 'on' ? 1 : 0;
// Data to be added
$form_data = [
'user_id' => Auth::user()->id,
'category_id' => $fields['category_id'],
'title' => $fields['title'],
'slug' => Str::slug($fields['title'], '-'),
'short_description' => $fields['short_description'],
'content' => $fields['content'],
'featured' => $fields['featured'],
'image' => $fields['image']
];
// Insert data in the 'articles' table
$query = Article::create($form_data);
if ($query) {
return redirect()->route('dashboard.articles')->with('success', 'The article titled "' . $form_data['title'] . '" was added');
} else {
return redirect()->back()->with('error', 'Adding article failed');
}
}
}
I call CKEditor like this:
<script>
CKEDITOR.replace('content', {
filebrowserUploadUrl: '{{route('dashboard.articles.ckupload').'?_token='.csrf_token()}}',
filebrowserUploadMethod: 'form'
});
CKEDITOR.on("instanceReady", function(event) {
event.editor.on("beforeCommandExec", function(event) {
// Show the paste dialog for the paste buttons and right-click paste
if (event.data.name == "paste") {
event.editor._.forcePasteDialog = true;
}
})
});
</script>
When trying to upload an image via CKEditor, I get this error in the browser console:
Uncaught ReferenceError: browser is not defined
When I use the "Upload" tab of the editor's dialog (see image below), I get the error
Failed to load resource: the server responded with a status of 500 (Internal Server Error)
I have fixed the issue by changing the ckupload
method to:
public function ckupload(Request $request)
{
if ($request->hasFile('upload')) {
$originName = $request->file('upload')->getClientOriginalName();
$fileName = pathinfo($originName, PATHINFO_FILENAME);
$extension = $request->file('upload')->getClientOriginalExtension();
$fileName = md5(time()) . Auth::user()->id . '.' . $extension;
$path = public_path('images/articles');
$fileName = $request->file('upload')->move($path, $fileName)->getFilename();
$CKEditorFuncNum = $request->input('CKEditorFuncNum');
$url = asset('images/articles/' . $fileName);
$response = "<script>window.parent.CKEDITOR.tools.callFunction($CKEditorFuncNum, '$url')</script>";
echo $response;
}
return false;
}
There is no need to call this method within the save()
method.