En mis proyectos suelo usar el editor CKeditor como editor WYSIWYG (What You See Is What You Get) para la parte de administración de contenidos y también para las cajas de comentarios, sin embargo si lo has usado sabrás que CKFinder, el plugin oficial para subir y administrar archivos en CkEditor es de pago y no siempre queremos realizar este pago. La alternativa que solía utilizar era la implementación de KcFinder que es una alternativa libre muy similar a CkFinder, pero la integración de la sesión con laravel no terminaba de convencerme, además me dio muchos problemas cuando actualice mis plataformas a Laravel 5.6, es por eso que decidí hacer la integración de los servicios con mi propio gestor de subidas de archivos vinculado a CKEditor, en este tutorial te enseñare a hacerlo de la misma manera que yo lo hice usando además de las librerías que ya indique el framework VeuJS2 que viene por defecto en laravel 5.x.
Voy a asumir que ya trabajas Laravel y tienes tu proyecto Montado, lo que vamos a hacer es crear un modelo con su respectiva migración que va a almacenar los archivos subidos.
$ php artisan make:model Attachment -m
Y a la migración le agrego unos atributos
public function up()
{
Schema::create('attachments', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
$table->string('file');
$table->integer('size');
$table->string('path');
$table->string('thumbnail');
$table->timestamps();
});
}
Como ven estoy haciendo referencia a un user_id para llevar el control sobre el espacio en disco que consume cada usuario.
Ejecutamos la migración
$ php artisan migrate Ahora vamos a implementar CK Editor, para hacerlo simplemente hago la instalación de este paquete https://github.com/dangvanthanh/vue-ckeditor2 con npm.
$ npm install vue-ckeditor2 --save Y luego agrego en la vista el CDN de CKEditor 4
<script src="//cdn.ckeditor.com/4.10.0/standard/ckeditor.js"></script>
Para que sea más fácil llamarlo en mis formularios de laravel/blade cree un componente de vue
// EditorComponent.vue
<template>
<div>
<vue-ckeditor v-model="content" :config="config" @blur="onBlur($event)" @focus="onFocus($event)" />
<textarea :name="name" v-model="content" class="d-none"></textarea>
</div>
</template>
<script>
import VueCkeditor from 'vue-ckeditor2';
export default {
props: ['name'],
components: { VueCkeditor },
data() {
return {
content: '',
config: {
height: 300,
filebrowserBrowseUrl: '/attachments',
filebrowserUploadUrl: '/attachments'
}
};
},
methods: {
onBlur(editor) {
//console.log(editor);
},
onFocus(editor) {
//console.log(editor);
}
}
};
</script>
Como ven en la configuración de CKEditor estamos agregando estos params
filebrowserBrowseUrl: '/attachments',
filebrowserUploadUrl: '/attachments'
Son las url con los callbacks para el plugin de administrador de archivos.
Ahora si vamos a crear el administrador de archivos, simplemente creamos un controlador que administre el modelo Attachment
$ php artisan make:controller AttachmentController --resources
Agregamos los sources para que nos cree los métodos index, create, store, edit, update y destroy. Y luego creamos un acceso en las rutas de nuestro proyecto
Route::resource('attachments', 'AttachmentController');
Así ya tendremos todas las opciones para administrar nuestros archivos, pero ya que los envió de las imágenes (es decir los métodos POST y PUT) son enviados por el plugin de CKEditor necesitamos desactivar la validación del campo CSRF de estas rutas, esto lo podemos hacer creando una excepción en el middleware de TokenVerification
app/Http/Middleware/VerifyCsrfToken.php
protected $except = [
‘attachments'
];
Volviendo al controlador de Attachments nos centraremos por ahora en dos métodos, el método index qué nos listará todas las imágenes del sitio, y el método store que recibirá las peticiones de CKeditor y subirá las imágenes.
public function index(Request $request)
{
$attachments = Attachment::all();
return view('admin.attachments.index', compact('attachments'));
}
public function store(Request $request)
{
$validatedData = $request->validate([
'upload' => 'required|mimes:jpeg,bmp,png,doc,docx,pdf,xls,xlsx,csv'
]);
$filename = str_slug(
explode(
'.',
$request->file('upload')
->getClientOriginalName())[0]
).'.'.$request->file('upload')
->getClientOriginalExtension();
if(Attachment::where('user_id', Auth::user()->id)
->where('name', $filename)->first()) {
$filename = str_slug(
explode(
'.',
$request->file('upload')
->getClientOriginalName()
)[0]).rand(10000,99999)
.'.'.$request->file('upload')
->getClientOriginalExtension();
}
//Create thumbnail
Image::make($request->file('upload'))->save('uploads/'.Auth::user()->id.'/'.$filename);
Image::make($request->file('upload'))->widen(300)->save('uploads/'.Auth::user()->id.'/thumb-'.$filename);
$attachment = new Attachment;
$attachment->user_id = Auth::user()->id;
$attachment->name = $filename;
$attachment->file = $filename;
$attachment->size = $request->file('upload')->getClientSize();
$attachment->path = '/uploads/'.Auth::user()->id.'/'.$filename;
$attachment->thumbnail = '/uploads/'.Auth::user()->id.'/thumb-'.$filename;
$attachment->save();
return response()->json([
'uploaded' => 1,
'fileName' => $filename,
'url' => '/uploads/'.Auth::user()->id.'/'.$filename
]);
}
En el método index solamente estamos llamando el listado completo de registros del modelo Attachment y lo pasamos en una variable a la vista.
Y en el método store que es más importante estamos usando varios elementos, primero mediante $request estamos obteniendo y procesando el archivo, validamos los mime type del archivo para que sea un archivo de imagen o documento válido, almacenamos el nombre del archivo en la variable $filename pasandola a slug con el helper de laravel str_slug(), validamos que no exista otro archivo con el mismo nombre y con la librería Intervention/Image almacenamos la imagen y creamos el thumbnail o imagen en miniatura con un prefijo. todo esto probablemente ya lo hayas hecho en tus proyectos, la parte importante es la respuesta del método, ya que debe enviar una respuesta a CKeditor, esta respuesta debe ser en formato json.
return response()->json([
'uploaded' => 1,
'fileName' => ‘nombreDelArchivo.jpg,
'url' => '/rl/donde/queda/publicado.jpg'
]);
En caso de querer validar y devolver un error puedes hacerlo de esta manera:
return response()->json([
'uploaded' => 0,
error => [message’ => ‘Mensaje de error’]
]);
En este punto la función de cargar imagen ya debería estar funcionando, pero al ir a “Ver servidor” no funcionará ya que nos falta aún crear la vista attachments/index.blade.php
Aunque podríamos crear una vista simple con el listado de los adjuntos vamos a usar vue también para poder crear filtros y poder manipular de una manera más “elegante” el listado. De manera que crearemos otro componente en vue y lo llamaremos desde la vista.
AttachmentsComponent.vue
<template>
<div class="card-columns">
<div class="card" v-for="attachment in attachmentsList">
<a v-on:click="use(attachment)">
<img :src="attachment.thumbnail" class="img-fluid">
</a>
<div class="card-footer">
{{ attachment.name }}
</div>
</div>
</div>
</template>
<script>
export default {
props: ['attachments'],
data() {
return {
attachmentsList: []
};
},
mounted() {
this.attachmentsList = this.attachments;
},
methods: {
use(attachment) {
window.opener.CKEDITOR.tools.callFunction( 1, attachment.path );
window.close();
}
}
};
</script>
<style scoped>
.card-columns {
column-count: 5;
}
</style>
Y en la vista simplemente llamamos el componente y le pasamos la colección de adjuntos:
<attachments :attachments="{{ $attachments }}"></attachments>
Las opciones de eliminar y editar las dejo para tu creatividad.