HEX
Server: LiteSpeed
System: Linux server315.web-hosting.com 4.18.0-553.54.1.lve.el8.x86_64 #1 SMP Wed Jun 4 13:01:13 UTC 2025 x86_64
User: globfdxw (6114)
PHP: 8.1.34
Disabled: NONE
Upload Files
File: //home/globfdxw/www/wp-content/plugins/wpforms-entry-automation/assets/js/connections.js
/* global wpforms_builder, wpf, wpforms_builder_providers, wpformsEntryAutomationBuilderVars */

// noinspection ES6ConvertVarToLetConst
var WPForms = window.WPForms || {}; // eslint-disable-line no-var
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};

/**
 * Handles admin functionalities for managing form connections in the WPForms builder interface.
 *
 * This module is responsible for initializing, managing, and processing
 * various connection-related capabilities in the WPForms builder interface.
 *
 * @since 1.0.0
 */
WPForms.Admin.Builder.Connections = WPForms.Admin.Builder.Connections || ( function( document, $ ) {
	let Cache;
	let Templates;

	return class Connections {
		/**
		 * Constructor for initializing the instance with specific settings and values.
		 *
		 * @since 1.0.0
		 *
		 * @param {Object} $holder                    The holder element to be used.
		 * @param {Object} [options={}]               Options object for customization.
		 * @param {string} [options.panel='settings'] The panel type to be used.
		 * @param {string} [options.entitySlug='']    The entity slug identifier.
		 */
		constructor( $holder, { panel = 'settings', entitySlug = '' } = {} ) {
			this.$holder = $holder;

			// We need to show him "Required fields empty" popup only once.
			this.isRequiredEmpty = false;
			this.panel = panel;
			this.entitySlug = entitySlug;
			this.isReady = false;

			this.selectors = {
				requiredFields: 'input.wpforms-required, select.wpforms-required, textarea.wpforms-required',
				requiredSelectField: 'select.wpforms-required',
				connection: '.wpforms-builder-entity-connection',
				connectionId: '.wpforms-builder-entity-connection-id',
				connectionList: '.wpforms-builder-entity-connections',
				addConnection: '.wpforms-builder-entity-connection-add',
				deleteConnection: '.wpforms-builder-entity-connection-delete',
				cloneConnection: '.wpforms-builder-entity-connection-clone',
				titleInput: '.wpforms-builder-entity-connection-title-input',
				emptyState: '.wpforms-entity-empty-state',
				connectionNameModal: '#wpforms-builder-entity-connection-name',
				exportTo: '.wpforms-entry-automation-export-to',
			};
		}

		/**
		 * Initializes the settings panel by checking the query string for a specific view
		 * and binding the ready event if the condition is met. Also binds actions to switch panels.
		 *
		 * @since 1.0.0
		 */
		init() {
			// We are requesting/loading a Settings panel.
			if ( wpf.getQueryString( 'view' ) === this.panel ) {
				$( this.ready.bind( this ) );
			}

			this.bindPanelSwitchAction();
		}

		/**
		 * Binds an event listener to handle actions when the panel is switched.
		 * The method listens for the 'wpformsPanelSwitched' event and invokes the `ready` method if the switched panel matches the current panel.
		 *
		 * @since 1.0.0
		 */
		bindPanelSwitchAction() {
			// We have switched panel.
			$( document ).on( 'wpformsPanelSwitched', ( event, panel ) => {
				if ( panel === this.panel ) {
					this.ready();
				}
			} );
		}

		/**
		 * Initializes the ready state of the object.
		 * Sets up the necessary conditions and binds required events if not already in the ready state.
		 *
		 * @since 1.0.0
		 */
		ready() {
			if ( this.isReady ) {
				return;
			}

			Cache = WPForms.Admin.Builder.Providers.cache;
			Templates = WPForms.Admin.Builder.Templates;

			this.bindEvents();
			this.initSortableConnections();
		}

		/**
		 * Binds event handlers to various DOM elements and actions.
		 * This includes form save notifications and click events for add, delete, and clone buttons.
		 *
		 * @since 1.0.0
		 */
		bindEvents() {
			$( document )
				.on( 'wpformsSaved', this.onFormSaved.bind( this ) )
				.on( 'wpformsBeforeSave', this.onBeforeFormSave.bind( this ) );

			// Add click event handler for the "Add New Connection" button.
			this.$holder.on( 'click', this.selectors.addConnection, ( e ) => {
				e.preventDefault();
				this.onAddConnection();
			} );

			// Add click event handler for the "Delete" button.
			this.$holder.on( 'click', this.selectors.deleteConnection, ( e ) => {
				e.preventDefault();
				this.onDeleteConnection( $( e.target ).closest( this.selectors.connection ) );
			} );

			// Add click event handler for the "Clone" button.
			this.$holder.on( 'click', this.selectors.cloneConnection, ( e ) => {
				e.preventDefault();
				this.onCloneConnection( $( e.target ).closest( this.selectors.connection ) );
			} );

			// Remove error class in required fields if a value is supplied.
			this.$holder.on( 'change', this.selectors.requiredSelectField, ( e ) => {
				this.validateRequiredField( e.target );
			} );

			// Validate required fields on input change.
			this.$holder.on( 'input', this.selectors.requiredFields, ( e ) => {
				this.validateRequiredField( e.target );
			} );
		}

		/**
		 * Initializes the sortable functionality for connections.
		 *
		 * @since 1.0.0
		 */
		initSortableConnections() {
			$( this.selectors.connectionList ).sortable( {
				items: `${ this.selectors.connection }:not(:first-child)`,
				handle: '.wpforms-builder-entity-connection-block-icon',
				start: ( event, ui ) => {
					ui.item.addClass( 'wpforms-builder-entity-connection-dragging' );
				},
				stop: ( event, ui ) => {
					ui.item.removeClass( 'wpforms-builder-entity-connection-dragging' );
				},
				update: ( event, ui ) => {
					this.enableQueue( ui.item );
				},
			} );
		}

		/**
		 * Enables the queue for a specific connection.
		 *
		 * @since 1.0.0
		 *
		 * @param {jQuery} $connection The jQuery object representing the connection to enable the queue for.
		 */
		enableQueue( $connection ) {
			// Enable queue for the connection.
			$connection.find( '.wpforms-entry-automation-schedule-queue' ).removeClass( 'wpforms-hidden' );
			$connection.find( '.wpforms-entry-automation-schedule-queue-settings' ).removeClass( 'wpforms-hidden' );

			const connectionId = $connection.data( 'connection_id' );

			$( `#wpforms-entry-automation-schedule-queue-${ connectionId }` ).prop( 'checked', true ).trigger( 'change' );
		}

		/**
		 * Retrieves all connection elements based on the defined selector.
		 *
		 * @since 1.0.0
		 *
		 * @return {jQuery} A jQuery object containing all elements matching the connection selector.
		 */
		getConnections() {
			return this.$holder.find( this.selectors.connection );
		}

		/**
		 * Handles the form save event, processes condition logic, and applies it to the connected elements.
		 *
		 * @since 1.0.0
		 *
		 * @param {Object} event The event object triggered by the form save action.
		 * @param {Object} data  The data object containing form submission details.
		 */
		onFormSaved( event, data ) {
			if ( ! Object.hasOwn( data, this.entitySlug ) ) {
				return;
			}

			const entity = data[ this.entitySlug ];

			if ( ! Object.hasOwn( entity, 'connections' ) ) {
				return;
			}

			// Clear HTML.
			this.getConnections().remove();
			// Save sanitized connections.
			Cache.set( this.entitySlug, 'connections', entity.connections );
			Cache.set( this.entitySlug, 'dropbox_folders', entity.dropbox_folders );
			// Render.
			this.generateConnections( data[ this.entitySlug ] );
		}

		/**
		 * Handles the event triggered before saving the form.
		 *
		 * @since 1.0.0
		 *
		 * @param {Object} event The event object triggered by the form save action.
		 */
		onBeforeFormSave( event ) {
			this.isRequiredEmpty = false;
			this.isDaysEmpty = false;
			this.ftpFailed = false;
			this.requiredThing = '';

			// By default - validate required fields.
			this.getConnections().each( ( _, connection ) => this.processConditionOnFormSave( connection ) );

			// Check if there are any required fields empty.
			if ( this.isRequiredEmpty || this.requiredThing || this.isDaysEmpty || this.ftpFailed ) {
				event.preventDefault();
			}

			this.notifyUser();
		}

		/**
		 * Notifies the user about empty required fields or FTP connection issues.
		 *
		 * @since 1.0.0
		 */
		notifyUser() {
			if ( this.isRequiredEmpty || this.requiredThing || this.isDaysEmpty ) {
				// Notify user.
				this.notifyUserAboutEmptyFields();
			}

			if ( this.ftpFailed ) {
				// Notify user about FTP connection requirement.
				this.notifyUserAboutFTPConnection();
			}
		}

		/**
		 * Validates if a required field is filled. Highlights the field with an error class if empty.
		 *
		 * @since 1.0.0
		 *
		 * @param {HTMLElement} field The field element to be validated.
		 */
		validateRequiredField( field ) {
			const $this = $( field );

			// If field is Choices.js element, validate it differently.
			if ( 'undefined' !== typeof $this.data( 'choicesjs' ) ) {
				this.validateStatusField( $this );

				return;
			}

			if ( ! $this.is( ':visible' ) && ! $this.hasClass( 'wpforms-smart-tags-widget-original' ) ) {
				return;
			}

			const value = $this.val();

			if ( ! _.isEmpty( value ) ) {
				$this.removeClass( 'wpforms-error' );

				return;
			}

			$this.addClass( 'wpforms-error' );

			this.isRequiredEmpty = true;
		}

		/**
		 * Validates the status field for Choices.js elements.
		 *
		 * @since 1.0.0
		 *
		 * @param {Object} $field The Choices.js field element to validate.
		 */
		validateStatusField( $field ) {
			const value = $field.data( 'choicesjs' ).getValue( true );

			const $element = $field.closest( '.choices__inner' );

			if ( ! _.isEmpty( value ) ) {
				$element.removeClass( 'wpforms-error' );

				return;
			}

			$element.addClass( 'wpforms-error' );

			this.isRequiredEmpty = true;
		}

		/**
		 * Gets the appropriate element to highlight for error display.
		 * For smart tags widgets, returns the widget container; otherwise returns the field itself.
		 *
		 * @since 1.0.0
		 * @deprecated 1.1.0
		 *
		 * @param {Object} $field The field element to check for error highlighting.
		 *
		 * @return {Object} The element that should receive the error highlight styling.
		 */
		getElementForErrorHighlight( $field ) {
			// eslint-disable-next-line no-console
			console.warn( 'WARNING! Function "getElementForErrorHighlight" is deprecated.' );

			return $field;
		}

		/**
		 * Processes specific conditions when saving a form by handling required fields
		 * and updating the summary text.
		 *
		 * @param {HTMLElement} connection The connection object used for interacting with the system.
		 */
		processConditionOnFormSave( connection ) { // eslint-disable-line no-unused-vars
			this.processRequiredFields( connection );
			this.processEntryInformationChange( connection );
		}

		/**
		 * Processes changes in entry information for a given connection.
		 *
		 * @since 1.0.0
		 *
		 * @param {HTMLElement} connection The connection element to be processed.
		 */
		processEntryInformationChange( connection ) {
			const $connection = $( connection );
			const connectionData = this.getConnectionData( $connection );

			// Skip If File Exists parameter is not Add Entries to file.
			if ( connectionData.connection?.on_duplicate !== 'add' ) {
				return;
			}

			const connectionId = this.getConnectionId( $connection );
			const additionalField = Cache.get( this.entitySlug, `connection_${ connectionId }_additional_field` );
			const formField = Cache.get( this.entitySlug, `connection_${ connectionId }_form_field` );

			let isChanged = false;

			// Check if the additional field have changed.
			if ( JSON.stringify( connectionData.connection.additional_field ) !== JSON.stringify( additionalField ) ) {
				isChanged = true;
			}

			// Check if the form field have changed.
			if ( JSON.stringify( connectionData.connection.form_field ) !== JSON.stringify( formField ) ) {
				isChanged = true;
			}

			if ( ! isChanged ) {
				return;
			}

			$.alert( {
				title: wpforms_builder.heads_up,
				content: wpformsEntryAutomationBuilderVars.columnsChanged,
				icon: 'fa fa-exclamation-circle',
				type: 'orange',
				buttons: {
					confirm: {
						text: wpforms_builder.ok,
						btnClass: 'btn-confirm',
						keys: [ 'enter' ],
					},
				},
			} );
		}

		/**
		 * Processes and validates the required fields in the given connection.
		 *
		 * @since 1.0.0
		 *
		 * @param {HTMLElement} connection The DOM element or container within which the required fields are checked.
		 */
		processRequiredFields( connection ) {
			// Do actually require fields checking.
			$( connection ).find( this.selectors.requiredFields ).each(
				( _, field ) => this.validateRequiredField( field )
			);

			const connectionId = $( connection ).data( 'connection_id' );

			if ( ! this.isDaysChecked( connectionId, connection ) ) {
				this.requiredThing = 'days_required';
			}

			this.validateFtpConnection( connectionId, connection );
		}

		/**
		 * Validates the FTP connection for a given connection ID.
		 *
		 * @since 1.0.0
		 *
		 * @param {string}      connectionId The ID of the connection to validate.
		 * @param {HTMLElement} connection   The connection element to validate.
		 */
		validateFtpConnection( connectionId, connection ) {
			if (
				$( this.selectors.exportTo, $( connection ) ).val() === 'ftp' &&
				$( `.wpforms-entry-automation-ftp-connection-remove-${ connectionId }` ).hasClass( 'wpforms-hidden' ) &&
				! this.isRequiredEmpty
			) {
				this.ftpFailed = true;
			}
		}

		/**
		 * Checks if any days are selected for a given connection.
		 *
		 * @since 1.0.0
		 *
		 * @param {string}      connectionId The ID of the connection to check.
		 * @param {HTMLElement} connection   The connection element to check for selected days.
		 *
		 * @return {boolean} Returns true if at least one day is checked, false otherwise.
		 */
		isDaysChecked( connectionId, connection ) {
			// If the days or the whole section with dates, frequency, days and time is hidden, return true to avoid validation.
			if ( $( connection ).find( '.wpforms-entry-automation-schedule-days, .wpforms-entry-automation-schedule-settings' ).hasClass( 'wpforms-hidden' ) ) {
				return true;
			}

			const days = $( `input[name^="settings[entry_automation][${ connectionId }][schedule][days]"]:checked` );

			return days.length > 0;
		}

		/**
		 * Notifies the user about empty required fields in a form by showing an alert message.
		 *
		 * @since 1.0.0
		 */
		notifyUserAboutEmptyFields() {
			let msg = wpforms_builder.provider_required_flds;

			if ( this.isDaysEmpty ) {
				msg = wpformsEntryAutomationBuilderVars.taskModal.days_required;
			}

			if ( this.requiredThing ) {
				msg = wpformsEntryAutomationBuilderVars.taskModal[ this.requiredThing ];
			}

			$.alert( {
				title: wpforms_builder.heads_up,
				content: msg.replace( '{provider}', '<strong>' + this.getEntityTitle() + '</strong>' ),
				icon: 'fa fa-exclamation-circle',
				type: 'red',
				buttons: {
					confirm: {
						text: wpforms_builder.ok,
						btnClass: 'btn-confirm',
						keys: [ 'enter' ],
					},
				},
			} );
		}

		/**
		 * Notifies the user about the requirement of an FTP connection.
		 *
		 * @since 1.0.0
		 */
		notifyUserAboutFTPConnection() {
			$.alert( {
				title: wpformsEntryAutomationBuilderVars.ftp.required.title,
				content: wpformsEntryAutomationBuilderVars.ftp.required.content,
				icon: 'fa fa-exclamation-circle',
				type: 'red',
				buttons: {
					confirm: {
						text: wpforms_builder.ok,
						btnClass: 'btn-confirm',
						keys: [ 'enter' ],
					},
				},
			} );
		}

		/**
		 * Retrieves and returns the text content of the entity title, after removing any buttons within the title area.
		 *
		 * @since 1.0.0
		 *
		 * @return {string} The trimmed text content of the entity title.
		 */
		getEntityTitle() {
			const $titleArea = this.$holder.find( '.wpforms-builder-entity-title' ).clone();
			$titleArea.find( 'button' ).remove();

			return $titleArea.text().trim();
		}

		/**
		 * Handles the addition of a new connection.
		 *
		 * @since 1.0.0
		 */
		onAddConnection() {
			this.addConnection().then( ( [ name ] ) => {
				this.connectionCreate( name );
				this.toggleEmptyState();
			} );
		}

		/**
		 * Handles the deletion of a connection and subsequent operations after deletion.
		 *
		 * @since 1.0.0
		 *
		 * @param {Object} $connection The connection object to be deleted.
		 */
		onDeleteConnection( $connection ) {
			this.deleteConnection( $connection )
				.then( ( [ connectionId ] ) => this.connectionDeleted( connectionId ) )
				.then( () => this.toggleEmptyState() );
		}

		/**
		 * Handles the deletion of a connection by updating the cache and managing the UI state accordingly.
		 *
		 * @since 1.0.0
		 *
		 * @param {string} connectionId The ID of the connection to be deleted.
		 */
		connectionDeleted( connectionId ) {
			Cache.deleteFrom( this.entitySlug, 'connections', connectionId );
		}

		/**
		 * Handle cloning of an existing connection.
		 *
		 * @since 1.0.0
		 *
		 * @param {jQuery} $connection The connection element to be cloned.
		 */
		onCloneConnection( $connection ) {
			// Extract connection name.
			const name = $connection.find( this.selectors.titleInput ).val() || '';
			const currentValues = this.getCurrentValues( $connection );
			const connectionId = currentValues.id;

			delete currentValues.id;
			delete currentValues.name;

			currentValues.clonedBy = connectionId;

			// Call connectionCreate with the cloned name and values.
			this.connectionCreate( name + ' cloned', currentValues );
		}

		/**
		 * Caches the current values of the specified connection.
		 *
		 * @since 1.0.0
		 *
		 * @param {Object} $connection The connection object to cache.
		 */
		cacheCurrentValues( $connection ) {
			const connectionId = this.getConnectionId( $connection );
			const connection = this.getCurrentValues( $connection );

			Cache.addTo( this.entitySlug, 'connections', connectionId, connection );
		}

		/**
		 * Extracts the current field values from a provided field container element
		 * and returns them in a structured JSON format.
		 * Processes nested field names to properly map their hierarchy in the resulting object.
		 *
		 * @since 1.0.0
		 *
		 * @param {Object} $connection A jQuery object containing the form or field container to extract values from.
		 *
		 * @return {Object} An object representing the current connection values with nested keys structured accordingly.
		 */
		getCurrentValues( $connection ) {
			const connectionId = this.getConnectionId( $connection );
			// Extract all field values into JSON format using serializeArray.
			const currentValues = {};
			const formData = $( '#wpforms-builder-form' ).serializeArray();

			formData.forEach( ( { name: fieldName, value } ) => {
				if ( ! fieldName ||
					! fieldName.includes( `[${ this.entitySlug }][${ connectionId }]` )
				) {
					return;
				}

				this.handleNestedFieldNames( fieldName, value, currentValues );
			} );

			return currentValues;
		}

		/**
		 * Handle nested field names in form data.
		 *
		 * @since 1.0.0
		 *
		 * @param {string} fieldName     The field name, potentially containing brackets for nesting.
		 * @param {string} value         The field value to be assigned.
		 * @param {Object} currentValues The object where values are collected.
		 */
		handleNestedFieldNames( fieldName, value, currentValues ) { // eslint-disable-line complexity
			// Handle regular field names (no brackets).
			if ( ! fieldName.includes( '[' ) ) {
				currentValues[ fieldName ] = value;
				return;
			}

			const parts = fieldName.match( /([^[\]]+)|\[([^[\]]*)\]/g ) || [];

			// Extract the root name and all keys.
			const keys = [];
			parts.forEach( ( part ) => {
				if ( part.startsWith( '[' ) ) {
					// This is a key inside brackets, remove the brackets.
					keys.push( part.slice( 1, -1 ) );
				} else {
					// This is the root name.
					keys.unshift( part );
				}
			} );

			// Check if this is the pattern: settings[entitySlug][id][field_name...].
			if ( keys[ 0 ] === 'settings' && keys.length >= 4 && keys[ 1 ] === this.entitySlug ) {
				// Skip the settings, entitySlug, and ID parts.
				const actualKeys = keys.slice( 3 );

				// Process the remaining keys.
				this.buildNestedStructure( actualKeys, value, currentValues );

				// Convert objects with numeric keys to arrays.
				this.convertObjectsToArrays( currentValues );
				return;
			}

			// Handle all other nested field patterns.
			this.buildNestedStructure( keys, value, currentValues );

			// Convert objects with numeric keys to arrays.
			this.convertObjectsToArrays( currentValues );
		}

		/**
		 * Recursively builds a nested object structure from an array of keys.
		 *
		 * @since 1.0.0
		 *
		 * @param {Array}  keys          Array of keys defining the path in the object.
		 * @param {*}      value         Value to set at the final path.
		 * @param {Object} currentValues Object where the nested structure is built.
		 */
		buildNestedStructure( keys, value, currentValues ) {
			if ( keys.length === 1 ) {
				// We've reached the final key, set the value.
				currentValues[ keys[ 0 ] ] = value;
				return;
			}

			const currentKey = keys[ 0 ];

			// If the current level doesn't exist, create it.
			if ( currentValues[ currentKey ] === undefined ) {
				currentValues[ currentKey ] = {};
			}

			// Recursively process the next level.
			this.buildNestedStructure( keys.slice( 1 ), value, currentValues[ currentKey ] );
		}

		/**
		 * Recursively converts objects with all numeric keys to arrays.
		 *
		 * @since 1.0.0
		 *
		 * @param {Object} obj The object to check and potentially convert.
		 *
		 * @return {Object|Array} The original object or a converted array.
		 */
		convertObjectsToArrays( obj ) {
			// Base case: if not an object or is null, return as is.
			if ( typeof obj !== 'object' || obj === null ) {
				return obj;
			}

			// Process each property recursively.
			Object.keys( obj ).forEach( ( key ) => {
				if ( typeof obj[ key ] === 'object' && obj[ key ] !== null ) {
					obj[ key ] = this.convertObjectsToArrays( obj[ key ] );
				}
			} );

			// Check if this object should be converted to an array.
			if ( this.shouldBeArray( obj ) ) {
				return Object.values( obj );
			}

			return obj;
		}

		/**
		 * Determines if an object should be converted to an array.
		 * An object should be an array if all its keys are numeric indices.
		 *
		 * @since 1.0.0
		 *
		 * @param {Object} obj The object to check.
		 *
		 * @return {boolean} True if the object should be an array, false otherwise.
		 */
		shouldBeArray( obj ) {
			const keys = Object.keys( obj );

			// If empty object or not an object, it's not a candidate for array conversion.
			if ( keys.length === 0 || typeof obj !== 'object' || obj === null ) {
				return false;
			}

			// Check if all keys are numeric.
			return keys.every( ( key ) => key === '' || /^\d+$/.test( key ) );
		}

		/**
		 * Creates a new connection with the specified name.
		 *
		 * @since 1.0.0
		 *
		 * @param {string} name            The name of the connection to be created.
		 * @param {Object} [currentValues] Optional. Values to pre-populate in the connection.
		 */
		connectionCreate( name, currentValues = {} ) {} // eslint-disable-line no-unused-vars

		/**
		 * Get default connection object.
		 *
		 * @since 1.0.0
		 * @param {string} name            The name of the connection to be created.
		 * @param {Object} [currentValues] Optional. Values to pre-populate in the connection.
		 */
		getDefaultConnection( name, currentValues = {} ) {} // eslint-disable-line no-unused-vars

		/**
		 * Displays a confirmation dialog for adding a new connection.
		 * Prompts the user to enter a connection name and performs validation.
		 * The resolved output will contain the entered connection name.
		 *
		 * @since 1.0.0
		 *
		 * @return {Promise<string[]>} A promise that resolves with an array containing the connection name entered by the user.
		 */
		addConnection() {
			const defaultValue = this.getDefaultConnectionName();
			const self = this;

			return new Promise( ( resolve ) => {
				$.confirm( {
					title: false,
					content: wpformsEntryAutomationBuilderVars.taskModal.prompt_connection +
						'<input ' + ( defaultValue === '' ? ' autofocus=""' : '' ) + 'type="text" id="wpforms-builder-entity-connection-name" placeholder="' + wpforms_builder_providers.prompt_placeholder + '" value="' + defaultValue + '">' +
						'<p class="error">' + wpformsEntryAutomationBuilderVars.taskModal.error_name + '</p>',
					icon: 'fa fa-info-circle',
					type: 'blue',
					buttons: {
						confirm: {
							text: wpforms_builder.ok,
							btnClass: 'btn-confirm',
							keys: [ 'enter' ],
							action() {
								const name = this.$content.find( '#wpforms-builder-entity-connection-name' ).val().trim(),
									error = this.$content.find( '.error' );

								if ( name === '' ) {
									error.show();
									return false;
								}

								resolve( [ name ] );
							},
						},
						cancel: {
							text: wpforms_builder.cancel,
						},
					},
					onContentReady() {
						// Update autofocus to be at the end of string when the default value is set.
						const input = $( self.selectors.connectionNameModal )[ 0 ];

						if ( input && defaultValue ) {
							input.setSelectionRange( defaultValue.length, defaultValue.length );
							input.focus();
						}
					},
				} );
			} );
		}

		/**
		 * Deletes a specified connection with confirmation dialog.
		 *
		 * Displays a confirmation dialog before deleting the connection. Upon confirmation,
		 * executes the provided callback function and removes the connection element.
		 *
		 * @since 1.0.0
		 *
		 * @param {jQuery}   $connection             The connection element to be deleted.
		 * @param {Function} [beforeDelete=() => {}] A callback function executed before the connection is removed.
		 *
		 * @return {Promise<void>} A promise that resolves when the connection has been successfully deleted.
		 */
		deleteConnection( $connection, beforeDelete = () => {} ) {
			let content = wpformsEntryAutomationBuilderVars.taskModal.confirm_connection;
			let type = 'orange';

			// Change the message if the connection is the first in the queue.
			if ( this.isQueueFirst( $connection ) ) {
				content = wpformsEntryAutomationBuilderVars.taskModal.queue_first;
				type = 'red';
			}

			return new Promise( ( resolve ) => {
				$.confirm( {
					title: false,
					content,
					icon: 'fa fa-exclamation-circle',
					type,
					buttons: {
						confirm: {
							text: wpforms_builder.ok,
							btnClass: 'btn-confirm',
							keys: [ 'enter' ],
							action() {
								beforeDelete();

								const connectionId = $connection.data( 'connection_id' );

								$connection.fadeOut( 'fast', function() {
									$( this ).remove();
									resolve( [ connectionId ] );
								} );
							},
						},
						cancel: {
							text: wpforms_builder.cancel,
						},
					},
				} );
			} );
		}

		/**
		 * Check if the connection is the first in the queue.
		 *
		 * @since 1.0.0
		 *
		 * @param {jQuery} $connection The connection element to check.
		 *
		 * @return {boolean} True if the connection is a parent of another queue connection, false otherwise.
		 */
		isQueueFirst( $connection ) {
			const connectionId = this.getConnectionId( $connection );
			const $toggle = $( `#wpforms-entry-automation-schedule-queue-${ connectionId }` );

			const numberOfConnections = this.getConnections().length;

			return ! $toggle.is( ':checked' ) && numberOfConnections > 1;
		}

		/**
		 * Get the default name for a new connection.
		 *
		 * @since 1.0.0
		 *
		 * @return {string} Returns the default name for a new connection.
		 */
		getDefaultConnectionName() {
			const numberOfConnections = this.getConnections().length;
			const defaultName = `${ this.getEntityTitle() } ${ wpformsEntryAutomationBuilderVars.taskModal.task_label }`;

			return ( numberOfConnections < 1 ? defaultName : '' ).trim();
		}

		/**
		 * Generates and processes connections based on the provided response data.
		 *
		 * @since 1.0.0
		 *
		 * @param {Object} responseData The response data containing connections to be processed.
		 *
		 * @return {Object} The updated response data.
		 */
		generateConnections( responseData ) {
			if ( _.isEmpty( responseData.connections ) ) {
				return responseData;
			}

			for ( const connectionID in responseData.connections ) {
				this.connectionGenerate( {
					connection: responseData.connections[ connectionID ],
				} );

				this.$holder.closest( '.wpforms-panel' ).trigger( 'connectionGeneralSettingsRendered', [ this.entitySlug, connectionID ] );
			}

			this.toggleEmptyState();

			// Show the Entry Automation Dashboard notice.
			$( '#wpforms-entry-automation-dashboard-notice' ).removeClass( 'wpforms-hidden' );

			return responseData;
		}

		/**
		 * Generates a connection based on the provided connection data.
		 *
		 * @since 1.0.0
		 *
		 * @param {Object} connectionData The data required to generate the connection.
		 */
		connectionGenerate( connectionData ) { // eslint-disable-line no-unused-vars
			// Implement.
		}

		/**
		 * Retrieves the connection ID associated with the provided element.
		 *
		 * @since 1.0.0
		 *
		 * @param {Object} $element The jQuery-wrapped DOM element from which the connection ID is to be retrieved.
		 *
		 * @return {string} The connection ID if found, otherwise undefined.
		 */
		getConnectionId( $element ) {
			const $connection = $element.closest( this.selectors.connection );

			return $connection.data( 'connection_id' );
		}

		/**
		 * Retrieves connection data for a given element.
		 *
		 * @since 1.0.0
		 *
		 * @param {Object} $element The element to retrieve the connection data for.
		 *
		 * @return {Object} An object containing the connection data.
		 */
		getConnectionData( $element ) {
			const connectionId = this.getConnectionId( $element );

			return {
				connection: Cache.getById( this.entitySlug, 'connections', connectionId ),
			};
		}

		/**
		 * Toggles the visibility of the empty state element based on the number of connections.
		 *
		 * @since 1.0.0
		 */
		toggleEmptyState() {
			const connections = this.getConnections();

			if ( connections.length > 0 ) {
				this.$holder.find( this.selectors.emptyState ).hide();

				return;
			}

			this.$holder.find( this.selectors.emptyState ).show();
		}

		/**
		 * Retrieves the error message element for the specified provider.
		 *
		 * @since 1.0.0
		 *
		 * @return {Object} The jQuery object containing the error message element for the provider.
		 */
		getEntityError() {
			return $( `#wpforms-${ this.entitySlug }-builder-provider-error` );
		}

		/**
		 * Displays an error message for the specified provider in the UI.
		 * This method checks if an error already exists for the provider and displays it.
		 * If no error exists, it dynamically creates and displays a new error template.
		 *
		 * @since 1.0.0
		 */
		showError() {
			const $error = this.getEntityError();

			if ( $error.length ) {
				$error.show();

				return;
			}

			const templateId = `wpforms-${ this.entitySlug }-builder-content-connection-default-error`;
			const $connections = this.$holder.find( '.wpforms-builder-entity-connections' );

			// Register and prepend template.
			Templates.add( [ templateId ] );
			$connections.prepend( Templates.get( templateId )() );

			// Show error.
			this.getEntityError().show();
		}
	};
}( document, jQuery ) );