-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added ckeditor5-adapter-ckfinder/_src.
- Loading branch information
Showing
3 changed files
with
283 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/** | ||
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
*/ | ||
|
||
/** | ||
* @module adapter-ckfinder | ||
*/ | ||
|
||
export { default as UploadAdapter } from './uploadadapter'; |
190 changes: 190 additions & 0 deletions
190
packages/ckeditor5-adapter-ckfinder/_src/uploadadapter.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
/** | ||
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
*/ | ||
|
||
/* globals XMLHttpRequest, FormData */ | ||
|
||
/** | ||
* @module adapter-ckfinder/uploadadapter | ||
*/ | ||
|
||
import { Plugin } from 'ckeditor5/src/core'; | ||
import { FileRepository } from 'ckeditor5/src/upload'; | ||
|
||
import { getCsrfToken } from './utils'; | ||
|
||
/** | ||
* A plugin that enables file uploads in CKEditor 5 using the CKFinder server–side connector. | ||
* | ||
* See the {@glink features/images/image-upload/ckfinder "CKFinder file manager integration" guide} to learn how to configure | ||
* and use this feature as well as find out more about the full integration with the file manager | ||
* provided by the {@link module:ckfinder/ckfinder~CKFinder} plugin. | ||
* | ||
* Check out the {@glink features/images/image-upload/image-upload comprehensive "Image upload overview"} to learn about | ||
* other ways to upload images into CKEditor 5. | ||
* | ||
* @extends module:core/plugin~Plugin | ||
*/ | ||
export default class CKFinderUploadAdapter extends Plugin { | ||
/** | ||
* @inheritDoc | ||
*/ | ||
static get requires() { | ||
return [ FileRepository ]; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
static get pluginName() { | ||
return 'CKFinderUploadAdapter'; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
init() { | ||
const url = this.editor.config.get( 'ckfinder.uploadUrl' ); | ||
|
||
if ( !url ) { | ||
return; | ||
} | ||
|
||
// Register CKFinderAdapter | ||
this.editor.plugins.get( FileRepository ).createUploadAdapter = loader => new UploadAdapter( loader, url, this.editor.t ); | ||
} | ||
} | ||
|
||
/** | ||
* Upload adapter for CKFinder. | ||
* | ||
* @private | ||
* @implements module:upload/filerepository~UploadAdapter | ||
*/ | ||
class UploadAdapter { | ||
/** | ||
* Creates a new adapter instance. | ||
* | ||
* @param {module:upload/filerepository~FileLoader} loader | ||
* @param {String} url | ||
* @param {module:utils/locale~Locale#t} t | ||
*/ | ||
constructor( loader, url, t ) { | ||
/** | ||
* FileLoader instance to use during the upload. | ||
* | ||
* @member {module:upload/filerepository~FileLoader} #loader | ||
*/ | ||
this.loader = loader; | ||
|
||
/** | ||
* Upload URL. | ||
* | ||
* @member {String} #url | ||
*/ | ||
this.url = url; | ||
|
||
/** | ||
* Locale translation method. | ||
* | ||
* @member {module:utils/locale~Locale#t} #t | ||
*/ | ||
this.t = t; | ||
} | ||
|
||
/** | ||
* Starts the upload process. | ||
* | ||
* @see module:upload/filerepository~UploadAdapter#upload | ||
* @returns {Promise.<Object>} | ||
*/ | ||
upload() { | ||
return this.loader.file.then( file => { | ||
return new Promise( ( resolve, reject ) => { | ||
this._initRequest(); | ||
this._initListeners( resolve, reject, file ); | ||
this._sendRequest( file ); | ||
} ); | ||
} ); | ||
} | ||
|
||
/** | ||
* Aborts the upload process. | ||
* | ||
* @see module:upload/filerepository~UploadAdapter#abort | ||
*/ | ||
abort() { | ||
if ( this.xhr ) { | ||
this.xhr.abort(); | ||
} | ||
} | ||
|
||
/** | ||
* Initializes the XMLHttpRequest object. | ||
* | ||
* @private | ||
*/ | ||
_initRequest() { | ||
const xhr = this.xhr = new XMLHttpRequest(); | ||
|
||
xhr.open( 'POST', this.url, true ); | ||
xhr.responseType = 'json'; | ||
} | ||
|
||
/** | ||
* Initializes XMLHttpRequest listeners. | ||
* | ||
* @private | ||
* @param {Function} resolve Callback function to be called when the request is successful. | ||
* @param {Function} reject Callback function to be called when the request cannot be completed. | ||
* @param {File} file File instance to be uploaded. | ||
*/ | ||
_initListeners( resolve, reject, file ) { | ||
const xhr = this.xhr; | ||
const loader = this.loader; | ||
const t = this.t; | ||
const genericError = t( 'Cannot upload file:' ) + ` ${ file.name }.`; | ||
|
||
xhr.addEventListener( 'error', () => reject( genericError ) ); | ||
xhr.addEventListener( 'abort', () => reject() ); | ||
xhr.addEventListener( 'load', () => { | ||
const response = xhr.response; | ||
|
||
if ( !response || !response.uploaded ) { | ||
return reject( response && response.error && response.error.message ? response.error.message : genericError ); | ||
} | ||
|
||
resolve( { | ||
default: response.url | ||
} ); | ||
} ); | ||
|
||
// Upload progress when it's supported. | ||
/* istanbul ignore else */ | ||
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. | ||
* | ||
* @private | ||
* @param {File} file File instance to be uploaded. | ||
*/ | ||
_sendRequest( file ) { | ||
// Prepare form data. | ||
const data = new FormData(); | ||
data.append( 'upload', file ); | ||
data.append( 'ckCsrfToken', getCsrfToken() ); | ||
|
||
// Send request. | ||
this.xhr.send( data ); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/** | ||
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
*/ | ||
|
||
/* globals window, document */ | ||
|
||
/** | ||
* @module adapter-ckfinder/utils | ||
*/ | ||
|
||
const TOKEN_COOKIE_NAME = 'ckCsrfToken'; | ||
const TOKEN_LENGTH = 40; | ||
const tokenCharset = 'abcdefghijklmnopqrstuvwxyz0123456789'; | ||
|
||
/** | ||
* Returns the CSRF token value. The value is a hash stored in `document.cookie` | ||
* under the `ckCsrfToken` key. The CSRF token can be used to secure the communication | ||
* between the web browser and the CKFinder server. | ||
* | ||
* @returns {String} | ||
*/ | ||
export function getCsrfToken() { | ||
let token = getCookie( TOKEN_COOKIE_NAME ); | ||
|
||
if ( !token || token.length != TOKEN_LENGTH ) { | ||
token = generateToken( TOKEN_LENGTH ); | ||
setCookie( TOKEN_COOKIE_NAME, token ); | ||
} | ||
|
||
return token; | ||
} | ||
|
||
/** | ||
* Returns the value of the cookie with a given name or `null` if the cookie is not found. | ||
* | ||
* @param {String} name | ||
* @returns {String|null} | ||
*/ | ||
export function getCookie( name ) { | ||
name = name.toLowerCase(); | ||
const parts = document.cookie.split( ';' ); | ||
|
||
for ( const part of parts ) { | ||
const pair = part.split( '=' ); | ||
const key = decodeURIComponent( pair[ 0 ].trim().toLowerCase() ); | ||
|
||
if ( key === name ) { | ||
return decodeURIComponent( pair[ 1 ] ); | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* Sets the value of the cookie with a given name. | ||
* | ||
* @param {String} name | ||
* @param {String} value | ||
*/ | ||
export function setCookie( name, value ) { | ||
document.cookie = encodeURIComponent( name ) + '=' + encodeURIComponent( value ) + ';path=/'; | ||
} | ||
|
||
// Generates the CSRF token with the given length. | ||
// | ||
// @private | ||
// @param {Number} length | ||
// @returns {string} | ||
function generateToken( length ) { | ||
let result = ''; | ||
const randValues = new Uint8Array( length ); | ||
|
||
window.crypto.getRandomValues( randValues ); | ||
|
||
for ( let j = 0; j < randValues.length; j++ ) { | ||
const character = tokenCharset.charAt( randValues[ j ] % tokenCharset.length ); | ||
result += Math.random() > 0.5 ? character.toUpperCase() : character; | ||
} | ||
|
||
return result; | ||
} |