0
0
0
s2smodern

En este tutorial se mostrará la manera de integrar a Yii2 uno de los editores más usados actualmente, TinyMCE, ampliamente usado en diversos CMS como Joomla, Evernot y WordPress. Para este tutorial se hará uso de una extensión de Yii Framework llamada 2amigos/yii2-tinymce-widget

Para instalar esta extensión se corre el siguiente comando:

composer require 2amigos/yii2-tinymce-widget:~1.1 

Una vez instalado, su integración se realiza de la siguiente manera:

<?php...
use 2amigos\tinymce\TinyMCE...<?= $form->field($model, 'text')->widget(TinyMce::className(), [
    'options' => ['rows' => 6],
    'language' => 'es',
    'clientOptions' => [
        'plugins' => [
            "advlist autolink lists link charmap print preview anchor",
            "searchreplace visualblocks code fullscreen",
            "insertdatetime media table contextmenu paste"
        ],
        'toolbar' => "undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image"
    ]
]);?>

Dentro de la matriz de configuraciones se pueden establecer los siguientes valores:

options: Configuración común para todos los elementos HTML, en forma clave-valor.

language; Idioma de la interfaz de usuario del Widget.

clientOptions: Una matriz con la configuración para TinyMCE. Estas configuraciones son las mismas que se describen en el sitio oficial del plugin.


Para subir imágenes desde el editor y que estén disponibles, hay que crear un método action en un controlador que permita subir el archivo y notificar al editor TinyMCE la ruta o el fallo. Si se usa en más de una vista el editor, lo recomendable es usar un controlador común a todos, en este ejemplo sería SiteController. Para un caso más específico, la plantilla de Yii2 usada es la avanzada la cual separa parte pública y administrativa pero puede usarse también con la plantilla básica.

El método puede ser como el siguiente:

    /**
     * Sube el archivo via AJAX desde el editor TinyMCE.
     *
     * Valida la extensión y el mime type para que sean solo imágenes
     * @return mixed
     */
    public function actionUploadImage()
    {
        \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
        if (Yii::$app->request->isAjax && Yii::$app->request->isPost) {
            $imageFile = \yii\web\UploadedFile::getInstanceByName("file");
            if (null !== $imageFile) {
                $extensions = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg'];
                $mimeTypes = ['image/png', 'image/jpg', 'image/jpeg', 'image/gif',
                    'image/bmp', 'image/svg+xml'];
                if (in_array($imageFile->type, $mimeTypes) || in_array($imageFile->extension, $extensions)) {
                    $fileName = $imageFile->baseName . '.' . $imageFile->extension;
                    $frontendDirectory = \Yii::getAlias("@frontend") . "/web/imagenes/";
                    $backendDirectory = \Yii::getAlias("@backend") . "/web/imagenes/";
// Para la plantilla básica:
// $directory = \Yii::getAlias("@webroot") . "/imagenes/";
                    $imageFile->saveAs($frontendDirectory . $imageFile->baseName . '.' . $imageFile->extension, false);
                    $imageFile->saveAs($backendDirectory . $imageFile->baseName . '.' . $imageFile->extension);
// Para la plantilla avanzada
                    return $this->asJson(["success" => true, 'location' => \Yii::$app->urlManagerFrontend->createAbsoluteUrl('/imagenes/' . $fileName)]); //
// para la plantilla básica
//return $this->asJson(["success" => true, 'location' => \Yii::$app->urlManager->createAbsoluteUrl('/imagenes/' . $fileName)]); //
                }
            }
        }

        return $this->asJson(["success" => false, 'error' => 'No se pudo subir la imagen.']);
    }

El método primero registra el formato de respuesta a JSON, luego valida si se está haciendo un llamado desde AJAX y si se están enviando datos a través de un formulario POST; luego obtiene una instancia del objeto UploadeFile el cual administra la carga de archivos y si este es una instancia, valida el formato del archivo, este debe ser un formato de imagen, lo valida por extensión y tipo mime y si son válidos, para la plantilla avanzada, obtiene las rutas absolutas al directorio /web/imagenes/ del frontend y backend y procede a mover el archivo subido a cada directorio.

Como se ve en el código, para la plantilla avanzada, se está usando \Yii::$app->urlManagerFrontend para generar una URL absoluta para el frontend. Esto se hace en el archivo principal de configuración de la aplicación (en este caso, backend):

...        
// backend
        'urlManager' => [
            'enablePrettyUrl' => false,
            'showScriptName' => false,
            'rules' => [
            ],
        ],
// frontend
        'urlManagerFrontend' => [
            'class' => 'yii\web\UrlManager',
            'baseUrl' => "//" . filter_input(INPUT_SERVER, 'SERVER_NAME'), // se usa el nombre del servidor web, ej: http://miyiiproject.com/
            'enablePrettyUrl' => true,
            'showScriptName' => false,
            'rules' => [
            ],
        ],

...

 Esta ruta obtenida es la que se envía como respuesta en formato JSON al editor.


Dentro de la configuración del editor (clave clientOptions) configuramos lo siguiente:

...
            'relative_urls' => false, // URL absolutas
            'remove_script_host' => false, // Toda la URL que incluya el nombre del sitio web
            'convert_urls' => true,
            'file_picker_types' => 'image',
            // and here's our custom image handler
            'images_upload_handler' => new JsExpression("function (blobInfo, success, failure) { " // código JS para hacer llamadas AJAX al método del controlador.
                    . " var xhr, formData;
                        xhr = new XMLHttpRequest();
                        xhr.withCredentials = false;
                        xhr.open('POST', '" . yii\helpers\Url::to(['/site/upload-image']) . "');
                        xhr.setRequestHeader('X-CSRF-Token', '" . \Yii::$app->request->getCsrfToken() . "'); // SE TIENE QUE ENVIAR EL TOKEN DE SESIÓN
                        xhr.onload = function() {
                            var json;
                            if (xhr.status != 200) {
                                failure('HTTP Error: ' + xhr.status);
                                return;
                            }
                            json = JSON.parse(xhr.responseText);

                            if(!json || typeof json.error == 'string') {
                                failure(json.error);
                                return;
                            }
                            success(json.location);
                        };
                        formData = new FormData();
                        formData.append('file', blobInfo.blob(), blobInfo.filename());
                        xhr.send(formData);
            }"),
...

Primero se deben permitir las URL absolutas. Por defecto, TinyMCE convierte la URL a una ruta relativa, y si se está en la plantilla avanzada, esto no permite ver la imagen tanto en el editor como en el contenido del frontend/backend, de igual modo se inhabilita la opción de eliminar el nombre de dominio. Se habilita la opción convert_urls, esta opción permite controlar si TinyMCE debe restaurar las URL a los valores originales. Luego se establece el parámetro file_picker_types a image (puede ser también media. Para este caso se deberían validar también los archivos de audio o video desde el método del controlador).

Por último, images_upload_handler es la opción que permite interactuar con el método del controlador SiteController. En esta opción se usa Javascript para enviar el archivo seleccionado. Dentro de los parámetros POST enviados, es obligatorio enviar el token de sesión ya que de lo contrario, Yii2 generará un error 400 diciendo que no se pueden leer todos los parámetros. Este truco fue obtenido desde Stack Overflow.