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/src/Export/Provider/FTP.php
<?php

namespace WPFormsEntryAutomation\Export\Provider;

use WP_Filesystem_FTPext;
use WPFormsEntryAutomation\Plugin;
use WPForms\Helpers\File;

/**
 * FTP provider implementation.
 *
 * @since 1.0.0
 */
class FTP extends Provider {

	/**
	 * Get a human-readable title of the provider.
	 *
	 * @since 1.0.0
	 *
	 * @return string Provider title.
	 */
	public function get_title(): string {

		return esc_html__( 'FTP Server', 'wpforms-entry-automation' );
	}

	/**
	 * Registers hooks for handling FTP connection testing via AJAX.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public function hooks(): void {

		$namespace_slug = Plugin::SLUG;
		// Test FTP connection.
		add_filter( "wpforms_builder_ajax_{$namespace_slug}_test_ftp_connection", [ $this, 'test_ftp_connection' ] );
	}

	/**
	 * Deliver formatted entries via FTP.
	 *
	 * @since 1.0.0
	 *
	 * @param string $entries_file_path Formatted entries.
	 * @param array  $connection        Connection data.
	 * @param array  $form_data         Form data.
	 *
	 * @return bool True on success, false on failure.
	 */
	public function deliver( string $entries_file_path, array $connection, array $form_data ): bool {
		// Get FTP settings.
		$ftp_settings = $this->get_ftp_settings( $connection );

		// Upload a file to the FTP server.
		$result = $this->upload_to_ftp( $entries_file_path, $ftp_settings );

		// Clean up a temporary file.
		wp_delete_file( $entries_file_path );

		if ( ! $result ) {
			wpforms_log(
				'Entry Automation - FTP upload failed',
				[
					'ftp_settings' => $ftp_settings,
					'file_path'    => $entries_file_path,
				],
				[
					'type'    => 'error',
					'form_id' => absint( $form_data['id'] ),
				]
			);
		}

		return $result;
	}

	/**
	 * Sanitize connection data based on the export method.
	 *
	 * @since 1.0.0
	 *
	 * @param array $connection_data Reference to the connection data array to sanitize.
	 * @param array $form_data       Form data array used for sanitization logic.
	 */
	public function sanitize_connection( array &$connection_data, array $form_data ): void {

		$connection_data['ftp'] = $connection_data['ftp'] ?? [];

		$this->sanitize_connection_fields( $connection_data['ftp'] );
	}

	/**
	 * Sanitize FTP connection data fields.
	 *
	 * @since 1.0.0
	 *
	 * @param array &$ftp_data Connection data array with fields to sanitize. Fields include:
	 *                         - 'host' (string): The host address.
	 *                         - 'port' (int): The port number.
	 *                         - 'password' (string): The password.
	 *                         - 'path' (string): The file path.
	 */
	private function sanitize_connection_fields( array &$ftp_data ): void {

		$port = (int) ( $ftp_data['port'] ?? '' );

		$ftp_data['host']     = sanitize_text_field( $ftp_data['host'] ?? '' );
		$ftp_data['port']     = ! empty( $port ) ? absint( $port ) : '';
		$ftp_data['password'] = sanitize_text_field( $ftp_data['password'] ?? '' );
		$ftp_data['path']     = sanitize_text_field( $ftp_data['path'] ?? '' );
	}

	/**
	 * Get FTP settings.
	 *
	 * @since 1.0.0
	 *
	 * @param array $task_data Task data.
	 *
	 * @return array FTP settings.
	 */
	private function get_ftp_settings( array $task_data ): array {

		$ftp = $task_data['ftp'] ?? [];

		if ( empty( $ftp ) ) {
			return [];
		}

		if ( empty( $ftp['host'] ) || empty( $ftp['username'] ) || empty( $ftp['password'] ) ) {
			return [];
		}

		return [
			'host'     => $ftp['host'],
			'port'     => ! empty( $ftp['port'] ) ? absint( $ftp['port'] ) : 21,
			'username' => $ftp['username'],
			'password' => $ftp['password'],
			'path'     => ! empty( $ftp['path'] ) ? $ftp['path'] : '/',
		];
	}

	/**
	 * Tests the FTP connection based on the provided settings.
	 *
	 * @since 1.0.0
	 *
	 * @param mixed $response The initial response or fallback response if the connection fails.
	 *
	 * @return bool|mixed True if the FTP connection is successful or the original response on failure.
	 */
	public function test_ftp_connection( $response ) {

		$settings = wp_unslash( $_POST['settings'] ?? [] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing

		if ( $this->check_ftp_connection( $settings ) ) {
			return true;
		}

		return $response;
	}

	/**
	 * Initialize FTP connection using the WP_Filesystem_FTPext class directly.
	 *
	 * @since 1.0.0
	 *
	 * @param array $ftp_settings FTP settings.
	 *
	 * @return object|bool FTP object on success, false on failure.
	 */
	private function init_ftp_connection( array $ftp_settings ) {

		// Include required files for WP_Filesystem_FTPext.
		require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
		require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-ftpext.php';

		// Prepare credentials for FTP connection.
		$credentials = [
			'hostname' => $ftp_settings['host'],
			'port'     => (int) $ftp_settings['port'],
			'username' => $ftp_settings['username'],
			'password' => $ftp_settings['password'],
		];

		// Initialize WP_Filesystem_FTPext directly.
		$ftp = new WP_Filesystem_FTPext( $credentials );

		// Try to connect.
		$connected = $ftp->connect();

		return $connected ? $ftp : false;
	}

	/**
	 * Check FTP connection.
	 *
	 * @since 1.0.0
	 *
	 * @param array $ftp_settings FTP settings.
	 *
	 * @return bool True if connection successful, false otherwise.
	 */
	private function check_ftp_connection( array $ftp_settings ): bool {

		// Validate FTP settings.
		if ( empty( $ftp_settings['host'] ) || empty( $ftp_settings['username'] ) || empty( $ftp_settings['password'] ) ) {
			return false;
		}

		// Initialize FTP connection.
		$ftp = $this->init_ftp_connection( $ftp_settings );

		if ( ! $ftp ) {
			return false;
		}

		// Check if we can access the target directory.
		$list = $ftp->dirlist( $ftp_settings['path'] );

		if ( $list === false ) {
			// If the directory is empty, create it.
			return $this->create_remote_directory( $ftp, $ftp_settings['path'] );
		}

		return true;
	}

	/**
	 * Upload a file to the FTP server.
	 *
	 * @since 1.0.0
	 *
	 * @param string $file_path    File path.
	 * @param array  $ftp_settings FTP settings.
	 *
	 * @return bool True on success, false on failure.
	 */
	private function upload_to_ftp( string $file_path, array $ftp_settings ): bool {

		// Check if a file exists.
		if ( ! file_exists( $file_path ) ) {
			return false;
		}

		// Get file contents.
		$file_contents = file_get_contents( $file_path );

		if ( $file_contents === false ) {
			return false;
		}

		// Initialize FTP connection.
		$ftp = $this->init_ftp_connection( $ftp_settings );

		if ( ! $ftp ) {
			return false;
		}

		$remote_path = rtrim( $ftp_settings['path'], '/' ) . '/';

		// Prepare a remote path.
		$remote_file = $remote_path . basename( $file_path );

		// Check if a file already exists on the server.
		if ( $ftp->size( $remote_file ) ) {
			// If a file exists, delete it.
			$ftp->delete( $remote_file, false, 'f' );
		}

		// Create a directory if it doesn't exist.
		$this->create_remote_directory( $ftp, $remote_path );

		// Upload file.
		return $ftp->put_contents( $remote_file, $file_contents );
	}

	/**
	 * Check if a file exists on the FTP server.
	 *
	 * @since 1.0.0
	 *
	 * @param string $file_name  Name of the file to check.
	 * @param array  $task_data  Task-specific data.
	 * @param array  $connection Connection details.
	 *
	 * @return bool True if the file exists, false otherwise.
	 */
	public function is_file_exist( string $file_name, array $task_data, array $connection ): bool {

		// Get FTP settings.
		$ftp_settings = $this->get_ftp_settings( $connection );

		// Initialize FTP connection.
		$ftp = $this->init_ftp_connection( $ftp_settings );

		if ( ! $ftp ) {
			return false;
		}

		$remote_path = rtrim( $ftp_settings['path'], '/' ) . '/';

		// Prepare a remote path.
		$remote_file = $remote_path . $file_name;

		return $ftp->size( $remote_file );
	}

	/**
	 * Create directories recursively on the remote server.
	 *
	 * @since 1.0.0
	 *
	 * @param object $ftp      WP_Filesystem_FTPext object.
	 * @param string $dir_path Directory path to create.
	 *
	 * @return bool True on success, false on failure.
	 *
	 * @noinspection PhpMissingParamTypeInspection
	 */
	private function create_remote_directory( $ftp, string $dir_path ): bool {

		// If the directory already exists, return true.
		if ( $ftp->is_dir( $dir_path ) ) {
			return true;
		}

		// Create the parent directory first.
		$parent_dir = dirname( $dir_path );

		if ( $parent_dir !== $dir_path && ! $ftp->is_dir( $parent_dir ) ) {
			$this->create_remote_directory( $ftp, $parent_dir );
		}

		// Create the directory.
		return $ftp->mkdir( $dir_path );
	}

	/**
	 * Download a file from the FTP server.
	 *
	 * @since 1.0.0
	 *
	 * @param array $task_data  Task data.
	 * @param array $connection Connection details.
	 *
	 * @return string Local file path or empty string on failure.
	 */
	public function download_file( array $task_data, array $connection ): string {

		// Get the FTP settings from connection details.
		$ftp_settings = $this->get_ftp_settings( $connection );

		// Initialize FTP connection.
		$ftp = $this->init_ftp_connection( $ftp_settings );

		if ( ! $ftp ) {
			return '';
		}

		// Prepare a remote file path.
		$remote_path = rtrim( $ftp_settings['path'], '/' ) . '/';
		$remote_file = $remote_path . $task_data['file_name'];

		$wp_filesystem = File::get_filesystem();
		$upload_dir    = File::get_upload_dir();
		$copied_file   = $ftp->get_contents( $remote_file );
		$folder        = $upload_dir . 'tmp';

		// Create a temporary file for download.
		$local_file = $folder . '/' . $task_data['file_name'];

		$result = $wp_filesystem->put_contents( $local_file, $copied_file );

		return $result === false ? '' : $local_file;
	}

	/**
	 * Delete file from FTP server.
	 *
	 * @since 1.0.0
	 *
	 * @param array $task_data  Task data.
	 * @param array $connection Connection details.
	 */
	public function delete_file( array $task_data, array $connection ): void {

		// Get the FTP settings from connection details.
		$ftp_settings = $this->get_ftp_settings( $connection );

		// Initialize FTP connection.
		$ftp = $this->init_ftp_connection( $ftp_settings );

		if ( ! $ftp ) {
			return;
		}

		// Prepare a remote file path.
		$remote_path = rtrim( $ftp_settings['path'], '/' ) . '/';
		$remote_file = $remote_path . $task_data['file_name'];

		// Delete the file.
		$ftp->delete( $remote_file );
	}
}