Save CKEditor 5 data to MySQL database in CodeIgniter 4

CKEditor 5 offers user-friendly functionality and an array of features. While adding content to the editor, users can preview how it will appear on the page.

The editor allows users to effortlessly add paragraphs, headings, create lists, insert links, images, and much more.

For successful file upload, proper configuration of CKEditor is essential; otherwise, files may not load in the editor.

In this tutorial, I show how you can integrate CKEditor 5 with file upload and save its data to the MySQL database in CodeIgniter 4.

Save CKEditor 5 data to MySQL database in CodeIgniter 4


Table of Content

  1. Create a Table using Migration
  2. Create a Model
  3. Create Routes
  4. Create a Controller
  5. Create a View
  6. Output
  7. Conclusion

1. Create a Table using Migration

  • Generate the migration file for creating the messages table by running the command:
php spark migrate:create create_messages_table
  • Now, navigate to app/Database/Migrations/ folder from the project root.
  • Find a PHP file that ends with CreateMessagesTableand open it.
  • Inside the file, you will find the up() method. Define the table structure within this method.
  • Additionally, implement the down() method, which will be used to delete the messages table when undoing the migration.
<?php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class CreateMessagesTable extends Migration
{
     public function up(){
          $this->forge->addField([
               'id' => [
                     'type' => 'INT',
                     'constraint' => 5,
                     'unsigned' => true,
                     'auto_increment' => true,
               ],
               'subject' => [
                     'type' => 'VARCHAR',
                     'constraint' => '100',
               ],
               'message' => [
                     'type' => 'TEXT',
                     'null' => true,
               ],
          ]);
          $this->forge->addKey('id', true);
          $this->forge->createTable('messages');
     }

     public function down(){
          $this->forge->dropTable('messages');
     }
}
  • Run the migration –
php spark migrate

2. Create a Model

  • Generate the Messages model file by running the command:
php spark make:model Messages
  • Open the file located at app/Models/Messages.php file.
  • Inside the Messages.php file, locate the $allowedFields array. Specify the field names – ['subject', 'message'] – that are allowed to be set during the insert and update operations.
<?php

namespace App\Models;

use CodeIgniter\Model;

class Messages extends Model
{
     protected $DBGroup = 'default';
     protected $table = 'messages';
     protected $primaryKey = 'id';
     protected $useAutoIncrement = true;
     protected $insertID = 0;
     protected $returnType = 'array';
     protected $useSoftDeletes = false;
     protected $protectFields = true;
     protected $allowedFields = ['subject','message'];

     // Dates
     protected $useTimestamps = false;
     protected $dateFormat = 'datetime';
     protected $createdField = 'created_at';
     protected $updatedField = 'updated_at';
     protected $deletedField = 'deleted_at';

     // Validation
     protected $validationRules = [];
     protected $validationMessages = [];
     protected $skipValidation = false;
     protected $cleanValidationRules = true;

     // Callbacks
     protected $allowCallbacks = true;
     protected $beforeInsert = [];
     protected $afterInsert = [];
     protected $beforeUpdate = [];
     protected $afterUpdate = [];
     protected $beforeFind = [];
     protected $afterFind = [];
     protected $beforeDelete = [];
     protected $afterDelete = [];
}

3. Create Routes

  • Open app/Config/Routes.php file.
  • Define 3 routes –
    • / – This route will display the index view.
    • page/fileUpload – Configure this route as a POST type to handle the uploading of CKEditor selected files.
    • page/submitform – Use this route to handle form submissions.
$routes->get('/', 'PagesController::index');
$routes->post('page/fileUpload', 'PagesController::fileUpload');
$routes->post('page/submitform', 'PagesController::submitForm');

4. Create a Controller

  • Generate the PagesController controller by running the command:
php spark make:controller PagesController
  • Open app/Controllers/PagesController.php file.
  • Within the PagesController.php file, include the Messages model.

Create the following 3 methods –

  • index() – This method loads the index view.
  • fileUpload() – Use this method to handle the uploading of the selected file from CKEditor.

Inside the method, generate a new token hash and assign it to $data['token'] and define validation for file. Here, access the file using upload name.

If the file fails validation, assign 0 to $data['uploaded'] and an error message to $data['error']['message'].

If the file passes validation, upload it to the uploads folder, and assign the filename to $data['fileName'], 1 to $data['uploaded'], and the file path to $data['url'].

Return $data in JSON format.

  • submitForm() – Use this method to handle the submission of the <form>.

Define validation rules for the submitted form values. If the form values pass validation, insert a record into the messages table. Set a SESSION flash message with a success message and redirect to the / route.

<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use App\Models\Messages;

class PagesController extends BaseController
{
     public function index(){
          return view('index'); 
     }

     // Upload CKEditor file
     public function fileUpload(){

          $data = array();

          // Read new token and assign to $data['token']
          $data['token'] = csrf_hash();

          ## Validation
          $validation = \Config\Services::validation();

          $input = $validation->setRules([
               'upload' => 'uploaded[upload]|max_size[upload,2048]|ext_in[upload,jpeg,jpg,png],'
          ]);

          if ($validation->withRequest($this->request)->run() == FALSE){

               $data['uploaded'] = 0;
               $data['error']['message'] = $validation->getError('upload');// Error response

          }else{

               if($file = $this->request->getFile('upload')) {
                     if ($file->isValid() && ! $file->hasMoved()) {
                          // Get file name
                          $filename = $file->getName();
                          
                          // Get random file name
                          $newName = $file->getRandomName();

                          // Store file in public/uploads/ folder
                          $file->move('../public/uploads', $newName);

                          // File path to display preview
                          $filepath = base_url()."/uploads/".$newName;

                          // Response
                          $data['fileName'] = $filename;
                          $data['uploaded'] = 1;
                          $data['url'] = $filepath;

                     }else{
                          // Response
                          $data['uploaded'] = 0;
                          $data['error']['message'] = 'File not uploaded.';
                     } 
               }else{
                     // Response
                     $data['uploaded'] = 0;
                     $data['error']['message'] = 'File not uploaded.';
               }
          }
          return $this->response->setJSON($data);

     }

     public function submitForm(){

          $request = service('request');
          $postData = $request->getPost();

          // Validation
          $input = $this->validate([
                'subject' => 'required',
                'message' => 'required'
          ]);

          if (!$input) { // Not valid

                $data['validation'] = $this->validator;

                return redirect()->back()->withInput()->with('validation', $this->validator);

          }else{

                $messages = new Messages();

                $data = [
                     'subject' => $postData['subject'],
                     'message' => $postData['message']
                ];

                ## Insert Record
                if($messages->insert($data)){
                     // Set Session
                     session()->setFlashdata('message', 'Submitted Successfully!');
                     session()->setFlashdata('alert-class', 'alert-success');

                }else{
                     // Set Session
                     session()->setFlashdata('message', 'Not Submitted.');
                     session()->setFlashdata('alert-class', 'alert-danger');

                     return redirect()->route('/')->withInput(); 
                }

          }
          return redirect()->route('/'); 
     }

}

5. Create a View

Create a new index.php file in the app/Views/ folder.

HTML –

Display Bootstrap alert message if SESSION flash is set.

Create a <form method="post" action="<?=site_url('page/submitform')?>">. Within the <form>, add a hidden field to store the CSRF token hash, a textbox, and a textarea.

Initialize CKEditor on the textarea field. Also, create a submit button.


Script –

Include CKEditor library –

<script src="https://cdn.ckeditor.com/ckeditor5/35.4.0/classic/ckeditor.js"></script>

To enable file upload in CKEditor with CSRF protection, utilize the CustomUploadAdapter class. This class doesn’t require any modifications in your project; simply include it.

Create a file loader instance using the CustomUploadAdapterPlugin() function. Read the CSRF token name and hash from the hidden field, and assign the file upload URL to uploadURL using "<?=site_url('page/fileUpload')?>".

Instantiate an instance of CustomUploadAdapter() and pass the loader, uploadURL, csrfName, and csrfHash as parameters.

Initialize CKEditor on the editor class. Enable the CustomUploadAdapterPlugin in the editor by adding the following configuration option:

extraPlugins: [ CustomUploadAdapterPlugin ],

NOTE – If CSRF protection is disabled in your project, you can use uploadUrl directly to upload the file. However, be aware that when using uploadUrl, you won’t be able to set headers or pass the CSRF token.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Save CKEditor 5 data to MySQL database in CodeIgniter 4</title>

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" >

    <style type="text/css">
    .ck-editor__editable { 
         min-height: 250px;
    }
    </style>

</head>
<body>

    <div class="container">

        <div class="row">
             <div class="col-md-6 mt-5" style="margin: 0 auto;">

                  <?php 
                  // Display Response
                  if(session()->has('message')){
                  ?>
                       <div class="alert <?= session()->getFlashdata('alert-class') ?>">
                            <?= session()->getFlashdata('message') ?>
                       </div>
                  <?php
                  }
                  ?>

                  <?php $validation = \Config\Services::validation(); ?>

                  <form method="post" action="<?=site_url('page/submitform')?>" >

                       <!-- CSRF token --> 
                       <input type="hidden" class="txt_csrfname" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>" />

                       <div class="form-group mb-4">
                            <label class="control-label col-sm-2" for="subject">Subject:</label>
                            <div class="col-sm-10">
                                 <input type="text" class="form-control" id="subject" placeholder="Enter Subject" name="subject" value="<?= old('subject') ?>" >
                            </div>

                            <!-- Error -->
                            <?php if( $validation->getError('subject') ) {?>
                            <div class='text-danger mt-2'>
                                  * <?= $validation->getError('subject'); ?>
                            </div>
                            <?php }?>
                       </div>

                       <div class="form-group mb-4">
                            <label class="control-label col-sm-2" for="message">Message:</label>
                            <div class="col-sm-10">
                                  <textarea class="form-control editor" id="message" name="message"><?= old('message') ?></textarea>
                            </div>

                            <!-- Error -->
                            <?php if( $validation->getError('message') ) {?>
                            <div class='text-danger mt-2'>
                                  * <?= $validation->getError('message'); ?>
                            </div>
                            <?php }?>
                       </div>

                       <div class="form-group "> 
                            <div class="col-sm-offset-2 col-sm-10">
                                 <button type="submit" class="btn btn-info">Submit</button>
                            </div>
                       </div>
                  </form>
             </div>
        </div>

    </div>

    <!-- Script -->
    <script src="https://cdn.ckeditor.com/ckeditor5/35.4.0/classic/ckeditor.js"></script>
    <script type="text/javascript">

    // Custom CKEditor file upload Adapter
    class CustomUploadAdapter {
         constructor( loader,uploadURL,csrfName,csrfHash ) {
              // The file loader instance to use during the upload.
              this.loader = loader;

              this.uploadURL = uploadURL;// Upload url
              this.csrfName = csrfName;// CSRF name
              this.csrfHash = csrfHash;// CSRF token

         }

         // Starts the upload process.
         upload() {
              return this.loader.file
                  .then( file => new Promise( ( resolve, reject ) => {
                        this._initRequest();
                        this._initListeners( resolve, reject, file );
                        this._sendRequest( file );
                   } ) );
         }

         // Aborts the upload process.
         abort() {
              if ( this.xhr ) {
                   this.xhr.abort();
              }
         }

         // Initializes the XMLHttpRequest object using the URL passed to the constructor.
         _initRequest() {
              const xhr = this.xhr = new XMLHttpRequest();

              xhr.open( 'POST', this.uploadURL, true );
              xhr.responseType = 'json';
         }

         // Initializes XMLHttpRequest listeners.
         _initListeners( resolve, reject, file ) {
              const xhr = this.xhr;
              const loader = this.loader;
              const genericErrorText = `Couldn't upload file: ${ file.name }.`;

              xhr.addEventListener( 'error', () => reject( genericErrorText ) );
              xhr.addEventListener( 'abort', () => reject() );
              xhr.addEventListener( 'load', () => {
                   const response = xhr.response;

                   if ( !response || response.error ) {
                         return reject( response && response.error ? response.error.message : genericErrorText );
                   }

                   // Update CSRF Token
                   document.getElementsByName(this.csrfName)[0].value = response.token;

                   resolve( {
                        default: response.url
                   } );
              } );

              // user interface.
              if ( xhr.upload ) {
                   xhr.upload.addEventListener( 'progress', evt => {
                        if ( evt.lengthComputable ) {
                             loader.uploadTotal = evt.total;
                             loader.uploaded = evt.loaded;
                        }
                   } );
              }
         }

         // Prepares the data and sends the request.
         _sendRequest( file ) {
              // Prepare the form data.
              const data = new FormData();

              data.append( 'upload', file );

              // Pass CSRF token
              data.append( this.csrfName, this.csrfHash );

              // Send the request.
              this.xhr.send( data );
         }
    }

    function CustomUploadAdapterPlugin( editor ) {
         editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => {

              // CSRF hash
              var csrfName = document.getElementsByClassName('txt_csrfname')[0].name; 
              var csrfHash = document.getElementsByClassName('txt_csrfname')[0].value;

              // Upload URL
              var uploadURL = "<?=site_url('page/fileUpload')?>";

              return new CustomUploadAdapter( loader, uploadURL,csrfName,csrfHash );
         };
    }

    // Initialize CKEditor on 'editor' class
    ClassicEditor
        .create( document.querySelector( '.editor' ),{ 
              extraPlugins: [ CustomUploadAdapterPlugin ],
              // uploadUrl: "<?=site_url('page/fileUpload')?>",// If CSRF protection is not enabled and remove above CustomUploadAdapterPlugin plugin
        } )
        .then( editor => {
              console.log( editor );
        } )
        .catch( error => {
              console.error( error );
        } );
    </script>

</body>
</html>

6. Output

View Output


7. Conclusion

Directly read the value of initialized CKEditor element on <form > submit and insert a record in the database.

If you want to display stored data in the database to CKEditor then pass it to textarea, CKEditor will automatically format it after initialization.

If you found this tutorial helpful then don't forget to share.

Leave a Comment