PageSourceSearch

https://kelcible.fr/wp-content/plugins/ninja-forms-uploads/assets/…/controllers/fieldFile.js?ver=3.3.35

js kelcible.fr collected 2026-10-06 07:26:18 UTC 17,457 bytes, 547 lines download raw bytes

1(function( $ ) {
2	var nfRadio = Backbone.Radio;
3	var radioChannel = nfRadio.channel( 'file_upload' );
4
5	var fileModel = Backbone.Model.extend( {
6		id: 0,
7		name: '',
8		tmpName: '',
9		fieldID: 0
10	} );
11
12	var FileCollection = Backbone.Collection.extend( {
13		model: fileModel
14	} );
15
16	var fileView = Marionette.ItemView.extend( {
17		tagName: 'nf-section',
18		template: '#tmpl-nf-field-file-row',
19
20		events: {
21			'click .delete': 'clickDelete'
22		},
23
24		clickDelete: function( event ) {
25			radioChannel.trigger( 'click:deleteFile', event, this.model );
26		}
27
28	} );
29
30	var fileCollectionView = Marionette.CollectionView.extend( {
31		childView: fileView
32	} );
33
34	var uploadController = Marionette.Object.extend( {
35
36		jqXHR: [],
37		$progress_bars: [],
38		activeUploads: {},
39
40		initialize: function() {
41			this.listenTo( radioChannel, 'init:model', this.initFile );
42			this.listenTo( radioChannel, 'render:view', this.initFileUpload );
43			this.listenTo( radioChannel, 'click:deleteFile', this.deleteFile );
44			radioChannel.reply( 'validate:required', this.validateRequired );
45			radioChannel.reply( 'get:submitData', this.getSubmitData );
46		},
47
48		initFile: function( model ) {
49			model.set( 'uploadMulti', 1 != model.get( 'upload_multi_count' ) ) ;
50		},
51
52		renderView: function( view ) {
53			var el = $( view.el ).find( '.files_uploaded' );
54			view.fileCollectionView = new fileCollectionView( {
55				el: el,
56				collection: view.model.get( 'files' ),
57				thisModel: this.model
58			} );
59
60			view.model.bind( 'change:files', this.changeCollection, view );
61
62			/*
63			 * This radio responder is only necessary if we have Multi-Part Forms active.
64			 * Thankfully, it won't fire if the add-on isn't active.
65			 *
66			 * When we change our parts in a Multi-Part Form, re-render our file collection.
67			 */
68			view.listenTo( nfRadio.channel( 'nfMP' ), 'change:part', this.changeCollection, view );
69			
70			/*
71			 * This will be triggered when a repeater fieldset is adding a fieldset
72			 * It prevents the files data already loaded not to be removed from the view
73			 */
74			if(this.isInRepeaterFieldset(view.el) && view.model.attributes.files.models.length > 0){
75				view.fileCollectionView.render();
76			}
77		},
78
79		isInRepeaterFieldset: function( element ) {
80			return element.dataset.fieldId.includes('.') && element.dataset.fieldId.includes('_');
81		},
82
83		changeCollection: function() {
84			this.fileCollectionView.render();
85			//Set values in repeater when refreshing the files data view
86			nfRadio.channel( "field-repeater" ).trigger( 'set:value' );
87		},
88
89		getFieldID: function( e ) {
90			var $parent = $( e.target ).parents( '.field-wrap' );
91
92			return $parent.data( 'field-id' );
93		},
94
95		getProgressBar: function( e ) {
96			var fieldID = this.getFieldID( e );
97
98			return this.$progress_bars[ fieldID ];
99		},
100
101		resetProgress: function( e ) {
102			var self = this;
103			setTimeout( function() {
104				self.getProgressBar( e ).css( 'width', 0 );
105			}, 1500 );
106		},
107
108		/**
109		 * Keep a selection inside the field's maximum file count.
110		 *
111		 * Files that do not fit are dropped from the selection before anything is
112		 * uploaded, and the visitor is told inline. A refused file is not an error
113		 * on the field: the field is still holding a valid number of files, so it
114		 * must neither disable the submit button nor raise the form's "correct
115		 * errors" notice (#727).
116		 */
117		checkFilesLimit: function( view, e, data ) {
118			var limit = view.model.get( 'upload_multi_count' );
119
120			if ( 1 == limit ) {
121				return true;
122			}
123
124			var files = view.model.get( 'files' );
125			var held = files ? files.length : 0;
126			var remaining = limit - held;
127
128			if ( remaining <= 0 ) {
129				this.showLimitNotice( view, limit );
130
131				return false;
132			}
133
134			if ( data.files.length > remaining ) {
135				// Same array reference the plugin hands to _onAdd, so trimming it
136				// here keeps the surplus files from ever being uploaded.
137				data.files.splice( remaining, data.files.length - remaining );
138				this.showLimitNotice( view, limit );
139
140				return true;
141			}
142
143			this.clearLimitNotice( view );
144
145			return true;
146		},
147
148		/**
149		 * Inline, non-blocking notice that some files were refused by the limit.
150		 * Deliberately not routed through showError: that registers a field error,
151		 * which is what used to strand visitors on a perfectly valid field.
152		 */
153		showLimitNotice: function( view, limit ) {
154			var message = nf_upload.strings.file_limit_notice.replace( '%n', limit );
155			var $field = $( view.el );
156			var $notice = $field.find( '.nf-fu-limit-notice' );
157
158			if ( ! $notice.length ) {
159				$notice = $( '<div class="nf-error-msg nf-fu-limit-notice" role="status" aria-live="polite"></div>' );
160				$field.find( '.files_uploaded' ).after( $notice );
161			}
162
163			$notice.text( message );
164		},
165
166		clearLimitNotice: function( view ) {
167			$( view.el ).find( '.nf-fu-limit-notice' ).remove();
168		},
169
170		addActiveUpload: function( formID, fieldID ) {
171			if ( !this.activeUploads[ formID ] ) {
172				this.activeUploads[ formID ] = {};
173			}
174			if ( !this.activeUploads[ formID ][ fieldID ] ) {
175				this.activeUploads[ formID ][ fieldID ] = 1;
176			} else {
177				this.activeUploads[ formID ][ fieldID ] += 1;
178			}
179		},
180
181		removeActiveUpload: function( formID, fieldID ) {
182			if ( this.activeUploads[ formID ] && this.activeUploads[ formID ][ fieldID ] ) {
183				this.activeUploads[ formID ][ fieldID ] -= 1;
184				if ( this.activeUploads[ formID ][ fieldID ] < 1 ) {
185					delete this.activeUploads[ formID ][ fieldID ];
186					var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', fieldID );
187					if ( fieldModel ) {
188						var errors = fieldModel.get( 'errors' );
189						var targetError = errors.get( 'upload-in-progress' );
190						if ( targetError ) {
191							errors.remove( targetError );
192							fieldModel.set( 'errors', errors );
193							fieldModel.trigger( 'change:errors', fieldModel );
194						}
195					}
196					var formModel = nfRadio.channel( 'app' ).request( 'get:form', formID );
197					if ( formModel ) {
198						var fieldErrors = formModel.get( 'fieldErrors' );
199						if ( fieldErrors[ fieldID ] ) {
200							delete fieldErrors[ fieldID ][ 'upload-in-progress' ];
201							if ( 0 === _.size( fieldErrors[ fieldID ] ) ) {
202								delete fieldErrors[ fieldID ];
203							}
204						}
205					}
206				}
207				
208				// If no more uploads for this form, enable submit
209				if ( Object.keys( this.activeUploads[ formID ] ).length === 0 ) {
210					delete this.activeUploads[ formID ];
211					nfRadio.channel( 'form-' + formID ).trigger( 'enable:submit' );
212				}
213			}
214		},
215
216		hasActiveUploads: function( formID ) {
217			return this.activeUploads[ formID ] && Object.keys( this.activeUploads[ formID ] ).length > 0;
218		},
219
220		maybeSubmitFieldData: function( data ) {
221			if ( data.paramName === 'files-' + data.formData.field_id || data.paramName === 'files-' + data.formData.field_id + '[]' ) {
222				this.jqXHR[ data.formData.field_id ] = data.submit();
223			}
224		},
225
226		showError: function( error, view ) {
227			nfRadio.channel( 'fields' ).request( 'add:error', view.model.id, 'upload-file-error', error );
228		},
229
230		resetError: function( view, fieldID  ) {
231			nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, 'upload-file-error' );
232			this.$progress_bars[ fieldID ].css( 'width', 0 );
233			$( view.el ).find( '.nf-fu-button-cancel' ).hide();
234			var formID = view.model.get( 'formID' );
235			this.removeActiveUpload( formID, fieldID );
236		},
237
238		cancelUpload: function( e ) {
239			e.preventDefault();
240			var fieldID = e.data.view.model.id;
241			e.data.controller.jqXHR[ fieldID ].abort();
242			// Cleanup is handled by the fileuploadfail event handler on abort
243		},
244
245		isNonceValid: function( view ) {
246			var nonceExpiry = view.model.get( 'uploadNonceExpiry' );
247			if ( typeof nonceExpiry === 'undefined' || nonceExpiry === null ) {
248				return false
249			}
250
251			var now = Math.round( (new Date()).getTime() / 1000 );
252
253			return now < nonceExpiry;
254		},
255
256		getValidNonce: function( field_id ) {
257			return jQuery.post( {
258				url: nfFrontEnd.adminAjax + '?action=nf_fu_get_new_nonce',
259				type: 'POST',
260				data: {
261					'field_id': field_id
262				},
263				cache: false
264			} );
265		},
266
267		initFileUpload: function( view ) {
268			var fieldID = view.model.id;
269			var formID = view.model.get( 'formID' );
270			var nonce = view.model.get( 'uploadNonce' );
271			var $file = $( view.el ).find( '.nf-element' );
272			var $files_uploaded = $( view.el ).find( '.files_uploaded' );
273			this.$progress_bars[ fieldID ] = $( view.el ).find( '.nf-fu-progress-bar' );
274			var url = nfFrontEnd.adminAjax + '?action=nf_fu_upload';
275			var self = this;
276			var files = view.model.get( 'files' );
277
278			$( view.el ).find( 'button.nf-fu-button-cancel' ).on( 'click', { view: view, controller: self }, self.cancelUpload );
279
280			$( view.el ).find( 'button.nf-fu-fileinput-button' ).on( 'click', function() {
281				// Don't show required validation error on click of the file upload button
282				view.model.set( 'firstTouch', false );
283			} );
284
285			/*
286			 * Make sure that our files array isn't undefined.
287			 * If it is, set it to an empty array.
288			 */
289			files = files || [];
290
291			/*
292			 * If "files" isn't a collection, turn it into one.
293			 */
294			if ( ! ( files instanceof FileCollection ) ) {
295				files = new FileCollection( files );
296				view.model.set( 'files', files );
297			}
298
299			this.renderView( view );
300
301			var formData = {
302				form_id: formID,
303				field_id: fieldID,
304				nonce: nonce,
305				abort: false
306			};
307
308			$file.fileupload( {
309				url: url,
310				dataType: 'json',
311				messages: {
312					maxFileSize: nf_upload.strings.max_file_size_error.replace( '%n', view.model.get( 'max_file_size_mb' ) ),
313					minFileSize: nf_upload.strings.min_file_size_error.replace( '%n', view.model.get( 'min_file_size_mb' ) )
314				},
315				maxChunkSize: view.model.get( 'max_chunk_size' ),
316				dropZone: $( view.el ),
317				maxFileSize: view.model.get( 'max_file_size' ),
318				minFileSize: view.model.get( 'min_file_size' ),
319				add: function (e, data) {
320					$( view.el ).find( '.nf-fu-button-cancel' ).show();
321					formData.abort = false;
322					data.formData = formData;
323					if ( self.isNonceValid( view ) ) {
324						self.maybeSubmitFieldData( data );
325
326						return;
327					}
328
329					self.getValidNonce( formData.field_id ).done( function( response ) {
330						if ( !response.success ) {
331							self.showError( nf_upload.strings.upload_nonce_error, view );
332							return false;
333						}
334
335						view.model.set( 'uploadNonce', response.data.nonce );
336						view.model.set( 'uploadNonceExpiry', response.data.nonce_expiry );
337
338						data.formData.nonce = response.data.nonce;
339						self.maybeSubmitFieldData( data );
340					} );
341				},
342				change: function( e, data ) {
343					if ( !self.checkFilesLimit( view, e, data ) ) {
344						return false;
345					}
346					// Remove any errors on this field.
347					nfRadio.channel( 'fields' ).request( 'remove:error', view.model.get( 'id' ), 'required-error' );
348				},
349				drop: function( e, data ) {
350					if ( !self.checkFilesLimit( view, e, data ) ) {
351						return false;
352					}
353				},
354				done: function( e, data ) {
355					self.removeActiveUpload( formID, fieldID );
356
357					if ( !data.result || data.result === undefined ) {
358						self.showError( nf_upload.strings.unknown_upload_error, view );
359						self.resetProgress( e );
360						return;
361					}
362
363					if ( -1 === data.result ) {
364						self.showError( nf_upload.strings.upload_error, view );
365						self.resetProgress( e );
366						return;
367					}
368
369					// Check for errors
370					if ( data.result.errors.length ) {
371						$.each( data.result.errors, function( index, error ) {
372							self.showError( error, view );
373						} );
374					}
375
376					if ( data.result.data.files === undefined || !data.result.data.files.length ) {
377						self.resetProgress( e );
378
379						return;
380					}
381
382					var allowed = view.model.get( 'upload_multi_count' );
383					var limit = 1;
384
385					if ( 1 != allowed ) {
386						var uploaded = view.model.get( 'files' ).length;
387						limit = allowed - uploaded;
388
389						if ( limit <= 0 ) {
390							// The field is already holding its maximum, so this file
391							// simply is not added. That is not an error state (#727).
392							self.showLimitNotice( view, allowed );
393							self.resetProgress( e );
394							return;
395						}
396					}
397
398					$( view.el ).find( '.nf-fu-button-cancel' ).hide();
399
400					var count = 0;
401					$.each( data.result.data.files, function( index, file ) {
402						count++;
403						if ( count > limit ) {
404							return false;
405						}
406						files.add( new fileModel( { name: file.name, tmp_name: file.tmp_name, fieldID: fieldID } ) );
407					} );
408
409					view.model.set( 'files', files );
410					view.model.set( 'value', 1 );
411					view.model.trigger( 'change:files', view.model );
412
413					self.resetProgress( e );
414
415					nfRadio.channel( 'fields' ).trigger( 'change:field', view.el, view.model );
416				},
417				start: function() {
418					if ( 1 == view.model.get( 'upload_multi_count' ) ) {
419						// Remove the files uploaded display and reset the collection
420						$files_uploaded.empty();
421						files.reset();
422					}
423					nfRadio.channel( 'fields' ).request( 'remove:error', view.model.id, 'upload-file-error' );
424					self.addActiveUpload( formID, fieldID );
425					var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', fieldID );
426					if ( fieldModel ) {
427						var errors = fieldModel.get( 'errors' );
428						errors.add( { 'id': 'upload-in-progress', 'msg': nf_upload.strings.upload_in_progress } );
429						fieldModel.set( 'errors', errors );
430						fieldModel.trigger( 'change:errors', fieldModel );
431					}
432					var formModel = nfRadio.channel( 'app' ).request( 'get:form', formID );
433					if ( formModel ) {
434						var fieldErrors = formModel.get( 'fieldErrors' );
435						if ( 'undefined' === typeof fieldErrors[ fieldID ] ) {
436							fieldErrors[ fieldID ] = {};
437						}
438						fieldErrors[ fieldID ][ 'upload-in-progress' ] = nf_upload.strings.upload_in_progress;
439					}
440					nfRadio.channel( 'form-' + formID ).trigger( 'disable:submit', view.model );
441				},
442				progressall: function( e, data ) {
443					var progress = parseInt( data.loaded / data.total * 100, 10 );
444					self.getProgressBar( e ).css( 'width', progress + '%' );
445				}
446			} ).on( 'fileuploadprocessalways', function( e, data ) {
447				var index = data.index,
448					file = data.files[ index ];
449				if ( file.error ) {
450					nfRadio.channel( 'fields' ).request( 'add:error', view.model.id, 'upload-file-error', file.error );
451					self.removeActiveUpload( formID, fieldID );
452				}
453			} ).on( 'fileuploadchunkdone', function( e, data ) {
454				// Check for errors
455				if ( data.result.errors.length ) {
456					$.each( data.result.errors, function( index, error ) {
457						self.showError( error, view );
458					} );
459
460					self.jqXHR[ formData.field_id ].abort();
461					self.$progress_bars[  formData.field_id ].css( 'width', 0 );
462					self.removeActiveUpload( formID, fieldID );
463					formData.abort = true;
464					return;
465				}
466
467				var key = data.result.data.files[ 0 ][ 'new_tmp_key' ];
468				formData[ key ] = data.result.data.files[ 0 ][ 'tmp_name' ];
469			} ).on( "fileuploadchunksend", function( e, data ) {
470				if ( formData.abort === true ) {
471					return false;
472				}
473			} ).on( 'fileuploadfail', function( e, data ) {
474				if ( data.errorThrown === 'abort' ) {
475					// Upload was cancelled via the Cancel button — perform targeted cleanup.
476					// resetError() handles progress bar, cancel button visibility, and active upload tracking.
477					self.resetError( view, fieldID );
478					return false;
479				}
480				// Genuine upload failure — clean up tracking and progress display.
481				self.removeActiveUpload( formID, fieldID );
482				self.resetProgress( e );
483			} )
484				.prop( 'disabled', !$.support.fileInput )
485				.parent().addClass( $.support.fileInput ? undefined : 'disabled' );
486		},
487
488		getSubmitData: function( fieldData, field ) {
489			fieldData.files = field.get( 'files' );
490
491			return fieldData;
492		},
493
494		deleteFile: function( event, model ) {
495			event.preventDefault();
496			// Resolve the field before removing the model: removing it re-renders
497			// the uploaded-files list, which detaches event.target from the
498			// document and leaves closest() with nothing to walk up to (#727).
499			var $field = $( event.target ).closest( '.field-wrap' );
500			model.collection.remove( model );
501			// Removing a file puts the field back inside its limit, so any notice
502			// about refused files no longer describes the field (#727).
503			$field.find( '.nf-fu-limit-notice' ).remove();
504			// send off AJAX request to delete temp file or uploaded file
505
506			var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', model.get( 'fieldID' ) );
507			nfRadio.channel( 'fields' ).trigger( 'change:field', '', fieldModel );
508			// Remove any errors on this field.
509			nfRadio.channel( 'fields' ).request( 'remove:error', fieldModel, 'required-error' );
510		},
511
512		/**
513		 * Check files have been submitted successfully for required field check
514		 *
515		 * @param el
516		 * @param model
517		 * @returns {boolean}
518		 */
519		validateRequired: function( el, model ) {
520			if ( !model.get( 'firstTouch' ) ) {
521				model.set( 'firstTouch', true );
522				return true;
523			}
524
525			var files = model.get( 'files' );
526			if ( typeof files === 'undefined' || !files.length ) {
527				model.set( 'value', '' );
528				return false;
529			}
530
531			return true;
532		}
533
534	} );
535
536	new uploadController();
537
538	$( document ).ready( function() {
539		$( 'body' ).on( 'click', 'button.nf-fu-fileinput-button', function( e ) {
540			$( this ).parent().find( 'input.nf-element' ).click();
541		} );
542
543		$( document ).bind( 'drop dragover', function( e ) {
544			e.preventDefault();
545		} );
546	} );
547})( jQuery );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.