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.
Contents
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
, anddatabase.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
,andsecurity.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
CreateMessagesTable
and open it. - Define the table structure in the
up()
method. - Using the
down()
method deletemessages
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
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.