1 | /**
|
2 | * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
3 | * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
4 | */
|
5 |
|
6 | /* globals XMLHttpRequest, FormData */
|
7 |
|
8 | /**
|
9 | * @module adapter-ckfinder/uploadadapter
|
10 | */
|
11 |
|
12 | import { Plugin } from 'ckeditor5/src/core';
|
13 | import { FileRepository } from 'ckeditor5/src/upload';
|
14 |
|
15 | import { getCsrfToken } from './utils';
|
16 |
|
17 | /**
|
18 | * A plugin that enables file uploads in CKEditor 5 using the CKFinder server–side connector.
|
19 | *
|
20 | * See the {@glink features/images/image-upload/ckfinder "CKFinder file manager integration" guide} to learn how to configure
|
21 | * and use this feature as well as find out more about the full integration with the file manager
|
22 | * provided by the {@link module:ckfinder/ckfinder~CKFinder} plugin.
|
23 | *
|
24 | * Check out the {@glink features/images/image-upload/image-upload comprehensive "Image upload overview"} to learn about
|
25 | * other ways to upload images into CKEditor 5.
|
26 | *
|
27 | * @extends module:core/plugin~Plugin
|
28 | */
|
29 | export default class CKFinderUploadAdapter extends Plugin {
|
30 | /**
|
31 | * @inheritDoc
|
32 | */
|
33 | static get requires() {
|
34 | return [ FileRepository ];
|
35 | }
|
36 |
|
37 | /**
|
38 | * @inheritDoc
|
39 | */
|
40 | static get pluginName() {
|
41 | return 'CKFinderUploadAdapter';
|
42 | }
|
43 |
|
44 | /**
|
45 | * @inheritDoc
|
46 | */
|
47 | init() {
|
48 | const url = this.editor.config.get( 'ckfinder.uploadUrl' );
|
49 |
|
50 | if ( !url ) {
|
51 | return;
|
52 | }
|
53 |
|
54 | // Register CKFinderAdapter
|
55 | this.editor.plugins.get( FileRepository ).createUploadAdapter = loader => new UploadAdapter( loader, url, this.editor.t );
|
56 | }
|
57 | }
|
58 |
|
59 | /**
|
60 | * Upload adapter for CKFinder.
|
61 | *
|
62 | * @private
|
63 | * @implements module:upload/filerepository~UploadAdapter
|
64 | */
|
65 | class UploadAdapter {
|
66 | /**
|
67 | * Creates a new adapter instance.
|
68 | *
|
69 | * @param {module:upload/filerepository~FileLoader} loader
|
70 | * @param {String} url
|
71 | * @param {module:utils/locale~Locale#t} t
|
72 | */
|
73 | constructor( loader, url, t ) {
|
74 | /**
|
75 | * FileLoader instance to use during the upload.
|
76 | *
|
77 | * @member {module:upload/filerepository~FileLoader} #loader
|
78 | */
|
79 | this.loader = loader;
|
80 |
|
81 | /**
|
82 | * Upload URL.
|
83 | *
|
84 | * @member {String} #url
|
85 | */
|
86 | this.url = url;
|
87 |
|
88 | /**
|
89 | * Locale translation method.
|
90 | *
|
91 | * @member {module:utils/locale~Locale#t} #t
|
92 | */
|
93 | this.t = t;
|
94 | }
|
95 |
|
96 | /**
|
97 | * Starts the upload process.
|
98 | *
|
99 | * @see module:upload/filerepository~UploadAdapter#upload
|
100 | * @returns {Promise.<Object>}
|
101 | */
|
102 | upload() {
|
103 | return this.loader.file.then( file => {
|
104 | return new Promise( ( resolve, reject ) => {
|
105 | this._initRequest();
|
106 | this._initListeners( resolve, reject, file );
|
107 | this._sendRequest( file );
|
108 | } );
|
109 | } );
|
110 | }
|
111 |
|
112 | /**
|
113 | * Aborts the upload process.
|
114 | *
|
115 | * @see module:upload/filerepository~UploadAdapter#abort
|
116 | */
|
117 | abort() {
|
118 | if ( this.xhr ) {
|
119 | this.xhr.abort();
|
120 | }
|
121 | }
|
122 |
|
123 | /**
|
124 | * Initializes the XMLHttpRequest object.
|
125 | *
|
126 | * @private
|
127 | */
|
128 | _initRequest() {
|
129 | const xhr = this.xhr = new XMLHttpRequest();
|
130 |
|
131 | xhr.open( 'POST', this.url, true );
|
132 | xhr.responseType = 'json';
|
133 | }
|
134 |
|
135 | /**
|
136 | * Initializes XMLHttpRequest listeners.
|
137 | *
|
138 | * @private
|
139 | * @param {Function} resolve Callback function to be called when the request is successful.
|
140 | * @param {Function} reject Callback function to be called when the request cannot be completed.
|
141 | * @param {File} file File instance to be uploaded.
|
142 | */
|
143 | _initListeners( resolve, reject, file ) {
|
144 | const xhr = this.xhr;
|
145 | const loader = this.loader;
|
146 | const t = this.t;
|
147 | const genericError = t( 'Cannot upload file:' ) + ` ${ file.name }.`;
|
148 |
|
149 | xhr.addEventListener( 'error', () => reject( genericError ) );
|
150 | xhr.addEventListener( 'abort', () => reject() );
|
151 | xhr.addEventListener( 'load', () => {
|
152 | const response = xhr.response;
|
153 |
|
154 | if ( !response || !response.uploaded ) {
|
155 | return reject( response && response.error && response.error.message ? response.error.message : genericError );
|
156 | }
|
157 |
|
158 | resolve( {
|
159 | default: response.url
|
160 | } );
|
161 | } );
|
162 |
|
163 | // Upload progress when it's supported.
|
164 | /* istanbul ignore else */
|
165 | if ( xhr.upload ) {
|
166 | xhr.upload.addEventListener( 'progress', evt => {
|
167 | if ( evt.lengthComputable ) {
|
168 | loader.uploadTotal = evt.total;
|
169 | loader.uploaded = evt.loaded;
|
170 | }
|
171 | } );
|
172 | }
|
173 | }
|
174 |
|
175 | /**
|
176 | * Prepares the data and sends the request.
|
177 | *
|
178 | * @private
|
179 | * @param {File} file File instance to be uploaded.
|
180 | */
|
181 | _sendRequest( file ) {
|
182 | // Prepare form data.
|
183 | const data = new FormData();
|
184 | data.append( 'upload', file );
|
185 | data.append( 'ckCsrfToken', getCsrfToken() );
|
186 |
|
187 | // Send request.
|
188 | this.xhr.send( data );
|
189 | }
|
190 | }
|