Save CKEditor 5 data to MySQL database in CodeIgniter 4

CKEditor 5 is easy to use and has lots of features. User can preview the content how it will display on the page while adding data in the editor.

In the editor you can add paragraph, heading, create list, add links and images, etc.

You need to configure CKEditor for file upload otherwise, the file will not load in the editor.

In this tutorial, I show how you can add 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


Contents

  1. Database configuration
  2. Enable CSRF
  3. Create Table
  4. Model
  5. Routes
  6. Controller
  7. View
  8. Output
  9. Conclusion

1. Database configuration

  • Open .env file which is available at the project root.

NOTE – If dot (.) not added at the start then rename the file to .env.

  • Remove # from start of database.default.hostname, database.default.database, database.default.username, database.default.password, database.default.DBDriver, database.default.DBPrefix, and database.default.port.
  • Update the configuration and save it.
database.default.hostname = 127.0.0.1
database.default.database = codeigniterdb
database.default.username = root
database.default.password = root
database.default.DBDriver = MySQLi
database.default.DBPrefix =
database.default.port = 3306

2. Enable CSRF

  • Again open .env file.
  • Remove # from the start of the security.tokenName,security.headerName, security.cookieName, security.expires,and security.regenerate.
  • I update the security.tokenName value with 'csrf_hash_name'. With this name read CSRF hash. You can update it with any other value.
  • If you don’t want to regenerate CSRF hash after each request then set security.regenerate = false.
security.tokenName = 'csrf_hash_name' 
security.headerName = 'X-CSRF-TOKEN' 
security.cookieName = 'csrf_cookie_name' 
security.expires = 7200 
security.regenerate = true
  • Open app/Config/Filters.php file.
  • Uncomment 'csrf' in 'before' if commented.
// Always applied before every request
public $globals = [
    'before' => [
       // 'honeypot',
       'csrf',
       // 'invalidchars',
    ],
    'after' => [
       'toolbar',
       // 'honeypot',
       // 'secureheaders',
    ],
];

3. Create Table

  • Create a new table messages using migration.
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.
  • Define the table structure in the up() method.
  • Using the down() method delete messages table that calls when undoing 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

4. Model

  • Create Messages Model –
php spark make:model Messages
  • Open app/Models/Messages.php file.
  • In $allowedFields Array specify field names – ['subject','message'] that can be set during insert and update.

Completed Code

<?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 = [];
}

5. Routes

  • Open app/Config/Routes.php file.
  • Define 3 routes –
    • / – Display index view.
    • page/fileUpload – POST type route to upload CKEditor selected file.
    • page/submitform – Handle form submit.
$routes->get('/', 'PagesController::index');
$routes->post('page/fileUpload', 'PagesController::fileUpload');
$routes->post('page/submitform', 'PagesController::submitForm');

6. Controller

  • Create PagesController Controller –
php spark make:controller PagesController
  • Open app/Controllers/PagesController.php file.
  • Include Messages Model.

Create 3 methods –

  • index() – Load index view.
  • fileUpload() – Using this method upload the selected file from CKEditor.

Assign new token hash to $data['token'] and define validation for file. Here, access the file using upload name.

If not validated then assign 0 to $data['uploaded'] and an error message to $data['error']['message'].

If validated then upload the file to the uploads folder and assign $filename to $data['fileName'], 1 to $data['uploaded'], and $filepath to $data['url'].

Return $data in JSON format.

  • submitForm() – Using this method handle <form > submit.

Define validation for submitted values. If validated then insert a record in the messages table. Set SESSION flash with a success message and redirect to / route.

Completed Code

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

}

7. View

Create index.php file in app/Views/ folder.

HTML –

Display Bootstrap alert message if SESSION flash is set.

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

Initiailze CKEditor on textarea. Also, create a submit button.


Script –

Include CKEditor library –

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

To upload the CKEditor file with CSRF protection created CustomUploadAdapter Class.

Directly use the class in your project and you don’t need to modify it.

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

Create an instance of CustomUploadAdapter(). Here, pass loader, uploadURL,csrfName, and csrfHash.

Initialize CKEditor on the editor class. Enable CustomUploadAdapterPlugin in editor using extraPlugins

extraPlugins: [ CustomUploadAdapterPlugin ],

NOTE – You can also use uploadUrl to upload file but you cannot set header and pass CSRF token with it. Only use it when CSRF protection is disabled in your project.

Completed Code

<!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>

8. Output

View Output


9. 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