<?php
namespace WPO\WC\PDF_Invoices_Pro;

if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly

if ( ! class_exists( '\\WPO\\WC\\PDF_Invoices_Pro\\Cloud_Storage' ) ) :

class Cloud_Storage {

	/**
	 * Settings option
	 * 
	 * @var string
	 */
	public static $settings_option = 'wpo_wcpdf_cloud_storage_settings';
	
	/**
	 * Cloud API class name
	 * 
	 * @var string
	 */
	private $cloud_api_class = null;

	/**
	 * Construct
	 * 
	 * @return	void
	 */
	public function __construct() {
		// Abstract API class
		$this->maybe_init_cloud_api();
		
		// Registers settings
		add_action( 'admin_init', array( $this, 'init_settings' ) );
		//add_filter( 'sanitize_option_' . self::$settings_option, array( $this, 'maybe_unlink_service' ), 20, 3 );

		// hook into main pdf plugin settings
		add_filter( 'wpo_wcpdf_settings_tabs', array( $this, 'settings_tab' ) );
		// add unlink button
		add_action( 'wpo_wcpdf_before_settings', array( $this, 'unlink' ), 10, 1 );

		add_action( 'wpo_wcpdf_email_attachment', array( $this, 'upload_attachment'), 10, 3 );
		add_action( 'woocommerce_order_status_changed', array( $this, 'upload_by_status'), 10, 4 );
		add_action( 'wpo_wcpdf_cloud_storage_upload_document', array( $this, 'upload_document' ), 10, 4 );
		add_action( 'wpo_wcpdf_cloud_storage_upload_file', array( $this, 'upload_file' ), 10, 1 );
		add_action( 'load-edit.php', array( $this, 'bulk_export' ) );
		add_action( 'load-edit.php', array( $this, 'export_queue' ) );

		// Enqueue scripts
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
		
		// Export bulk actions
		add_action( 'bulk_actions-edit-shop_order', array( $this, 'export_actions' ), 30 );
		add_filter( 'bulk_actions-woocommerce_page_wc-orders', array( $this, 'export_actions' ), 30 ); // WC 7.1+
			
		// admin notices
		add_action( 'admin_notices', array( $this, 'upload_queue' ) );
		add_action( 'admin_notices', array( $this, 'service_requirements_not_met_notice' ) );
		add_action( 'admin_notices', array( $this, 'gdrive_announcement_notice' ) );
		
		add_action( 'wpo_wcpdf_settings_fields_debug', array( $this, 'manually_refresh_tokens_setting' ), 10, 4 );
		add_filter( 'wpo_wcpdf_settings_debug_categories', array( $this, 'manually_refresh_tokens_setting_category' ), 10, 1 );
		
		add_action( 'wpo_wcpdf_after_system_status_table', array( $this, 'cloud_requirements_table' ) );
		
		// import/export cloud storage settings
		add_filter( 'wpo_wcpdf_setting_types', array( $this, 'cloud_storage_setting_type' ), 10, 1 );
		add_filter( 'wpo_wcpdf_export_settings', array( $this, 'cloud_storage_settings_export' ), 10, 2 );
		add_filter( 'wpo_wcpdf_import_settings_option', array( $this, 'cloud_storage_settings_option_import' ), 10, 3 );
		add_filter( 'wpo_wcpdf_reset_settings_option', array( $this, 'cloud_storage_settings_option_reset' ), 10, 2 );
	}
	
	/**
	 * Get list of available cloud services
	 * 
	 * @link https://simpleicons.org/
	 *
	 * @return array
	 */
	public static function available_services(): array {
		return apply_filters( 'wpo_ips_pro_available_services', array(
			'ftp' => array(
				'name'         => 'FTP/SFTP',
				'logo'         => '',
				'active'       => true,
				'file'         => WPO_WCPDF_Pro()->plugin_path() . '/includes/cloud/ftp/ftp.php',
				'requirements' => array(
					'php' => '8.0.2'
				),
				'settings'     => array(
					array(
						'type'     => 'setting',
						'id'       => 'ftp_config',
						'title'    => __( 'Connection', 'wpo_wcpdf_pro' ),
						'callback' => array( '\WPO\WC\PDF_Invoices_Pro\Cloud\FTP\FTP_Upload', 'ftp_setting_callback' ),
						'section'  => 'cloud_storage_general_settings',
						'args'     => array(
							'option_name' => self::$settings_option,
							'id'          => 'ftp_config',
							'options'     => array(
								'ftp_protocol' => array(
									'title'    => __( 'Protocol', 'wpo_wcpdf_pro' ) . ': ',
									'callback' => 'select',
									'options'  => array(
										'ftp'  =>  'FTP',
										'sftp' =>  'SFTP',
									),
								),
								'ftp_host' => array(
									'placeholder' => __( 'Host (without protocol)', 'wpo_wcpdf_pro' ),
									'callback'    => 'text_input',
									'size'        => null,
								),
								'ftp_port' => array(
									'placeholder' => __( 'Port', 'wpo_wcpdf_pro' ),
									'callback'    => 'text_input',
									'type'        => 'number',
									'size'        => 4,
								),
								'ftp_username' => array(
									'placeholder' => __( 'Username', 'wpo_wcpdf_pro' ),
									'callback'    => 'text_input',
									'size'        => null,
								),
								'ftp_password' => array(
									'placeholder' => __( 'Password', 'wpo_wcpdf_pro' ),
									'callback'    => 'text_input',
									'type'        => 'password',
									'size'        => null,
								),
								'ftp_remote_folder' => array(
									'placeholder' => __( 'Remote folder (optional)', 'wpo_wcpdf_pro' ),
									'callback'    => 'text_input',
									'size'        => null,
									'description' => sprintf(
										/* translators: 1: example root path, 2: example remote folder, 3: resulting full path */
										__( 'Folder inside the root path where your documents will be saved. Example: Root path %1$s with remote folder %2$s stores files in %3$s.', 'wpo_wcpdf_pro' ),
										'<code>/</code>',
										'<code>uploads/invoices</code>',
										'<code>/uploads/invoices</code>'
									),
								),
								'ftp_root_path' => array(
									'placeholder' => __( 'Root path (optional)', 'wpo_wcpdf_pro' ),
									'callback'    => 'text_input',
									'size'        => null,
									'description' => sprintf(
										/* translators: 1: example root path, 2: example remote folder */
										__( 'Base directory for this SFTP user. Usually %1$s. Only change if your provider tells you a specific path (for example %2$s).', 'wpo_wcpdf_pro' ),
										'<code>/</code>',
										'<code>/uploads</code>'
									),
								),
							),
						),
					),
				),
			),
			'dropbox' => array(
				'name'         => 'Dropbox',
				'logo'         => '<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Dropbox</title><path d="M6 1.807L0 5.629l6 3.822 6.001-3.822L6 1.807zM18 1.807l-6 3.822 6 3.822 6-3.822-6-3.822zM0 13.274l6 3.822 6.001-3.822L6 9.452l-6 3.822zM18 9.452l-6 3.822 6 3.822 6-3.822-6-3.822zM6 18.371l6.001 3.822 6-3.822-6-3.822L6 18.371z"/></svg>',
				'active'       => true,
				'file'         => WPO_WCPDF_Pro()->plugin_path() . '/includes/cloud/dropbox/dropbox-api.php',
				'requirements' => array(
					'php' => '8.1'
				),
				'settings'     => array(
					array(
						'type'     => 'setting',
						'id'       => 'dropbox_access_type',
						'title'    => __( 'Destination folder', 'wpo_wcpdf_pro' ),
						'callback' => 'select',
						'section'  => 'cloud_storage_general_settings',
						'args'     => array(
							'option_name' => self::$settings_option,
							'id'          => 'dropbox_access_type',
							'options'     => array(
								'app_folder'  => __( 'App folder (restricted access)', 'wpo_wcpdf_pro' ),
								'root_folder' => __( 'Main Dropbox folder', 'wpo_wcpdf_pro' ),
							),
							'custom'      => array(
								'type'          => 'text_input',
								'custom_option' => 'root_folder',
								'args'          => array(
									'option_name' => self::$settings_option,
									'id'          => 'dropbox_destination_folder',
									'size'        => '40',
									'description' => sprintf(
										'%s<br>%s',
										__( 'Enter a folder name to use (optional)', 'wpo_wcpdf_pro' ),
										sprintf(
											/* translators: example folder/subfolder */
											__( 'You can also add nested directories, for example: %s', 'wpo_wcpdf_pro' ),
											'<code>folder/subfolder</code>'
										)
									),
								),
							),
							'description' => __( 'Selecting App folder keeps your files inside the plugin\'s private Dropbox folder. To save documents elsewhere, choose Main Dropbox folder and specify a folder name below.', 'wpo_wcpdf_pro' ),
						),
					),
				),
			),
			'gdrive' => array(
				'name'		    => 'Google Drive',
				'logo'		    => '<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Google Drive</title><path d="M12.01 1.485c-2.082 0-3.754.02-3.743.047.01.02 1.708 3.001 3.774 6.62l3.76 6.574h3.76c2.081 0 3.753-.02 3.742-.047-.005-.02-1.708-3.001-3.775-6.62l-3.76-6.574zm-4.76 1.73a789.828 789.861 0 0 0-3.63 6.319L0 15.868l1.89 3.298 1.885 3.297 3.62-6.335 3.618-6.33-1.88-3.287C8.1 4.704 7.255 3.22 7.25 3.214zm2.259 12.653-.203.348c-.114.198-.96 1.672-1.88 3.287a423.93 423.948 0 0 1-1.698 2.97c-.01.026 3.24.042 7.222.042h7.244l1.796-3.157c.992-1.734 1.85-3.23 1.906-3.323l.104-.167h-7.249z"/></svg>',
				'active'	    => true,
				'file'          => WPO_WCPDF_Pro()->plugin_path() . '/includes/cloud/gdrive/gdrive-api.php',
				'requirements'  => array(
					'php' => '8.1'
				),
				'settings'      => array(
					array(
						'type'     => 'setting',
						'id'       => 'gdrive_access_type',
						'title'    => __( 'Destination folder', 'wpo_wcpdf_pro' ),
						'callback' => 'select',
						'section'  => 'cloud_storage_general_settings',
						'args'     => array(
							'option_name' => self::$settings_option,
							'id'          => 'gdrive_access_type',
							'options'     => array(
								'app_folder'    => __( 'App folder', 'wpo_wcpdf_pro' ),
								'custom_folder' => __( 'Custom folder', 'wpo_wcpdf_pro' ),
							),
							'custom'      => array(
								'type'          => 'text_input',
								'custom_option' => 'custom_folder',
								'args'          => array(
									'option_name' => self::$settings_option,
									'id'          => 'gdrive_destination_folder',
									'size'        => '40',
									'description' => sprintf(
										'%s<br>%s',
										__( 'Enter a folder name to use (required).', 'wpo_wcpdf_pro' ),
										sprintf(
											/* translators: example folder/subfolder */
											__( 'You can also add nested directories, for example: %s', 'wpo_wcpdf_pro' ),
											'<code>folder/subfolder</code>'
										)
									),
								),
							),
							'description' => __( 'Selecting App folder keeps your files inside the plugin\'s private Google Drive folder. To save documents elsewhere, choose Custom folder and specify a folder name below.', 'wpo_wcpdf_pro' ),
						),
					),
				),
			),
		), self::$settings_option );
	}
	
	/**
	 * Maybe initialize the cloud API class
	 * 
	 * @return void
	 */
	private function maybe_init_cloud_api(): void {
		if ( ! class_exists( '\\WPO\\WC\\PDF_Invoices_Pro\\Cloud\\Abstracts\\Cloud_API' ) ) {
			include_once WPO_WCPDF_Pro()->plugin_path() . '/includes/cloud/abstracts/abstract-wcpdf-cloud-api.php';
		}
		
		if ( is_null( $this->cloud_api_class ) && class_exists( '\\WPO\\WC\\PDF_Invoices_Pro\\Cloud\\Abstracts\\Cloud_API' ) ) {
			$this->cloud_api_class = '\WPO\WC\PDF_Invoices_Pro\Cloud\Abstracts\Cloud_API';
			
			// include service API
			$this->include_service_api();
		}
	}
	
	/**
	 * Include service API file
	 *
	 * @return void
	 */
	public function include_service_api(): void {
		foreach ( self::available_services() as $slug => $service ) {
			if ( ! empty( $service['file'] ) && file_exists( $service['file'] ) ) {
				include_once $service['file'];
			}
		}
	}
	
	/**
	 * Check service requirements
	 *
	 * @param ?string $slug
	 * @return bool
	 */
	public function service_requirements_met( ?string $slug ): bool {
		if ( ! $slug ) {
			return false;
		}
		
		$requirements_met   = true;
		$available_services = self::available_services();
		$service            = isset( $available_services[ $slug ] ) ? $available_services[ $slug ] : false;
		
		if ( ! $service ) {
			return false;
		}
		
		if ( ! empty( $service['requirements'] ) && is_array( $service['requirements'] ) ) {
			foreach ( $service['requirements'] as $requirement => $value ) {
				switch ( $requirement ) {
					case 'php':
						if ( version_compare( PHP_VERSION, $value, '<' ) ) {
							$requirements_met = false;
						}
						break;
				}
			}
		}
		
		if ( $requirements_met ) {
			delete_option( 'wpo_wcpdf_cloud_service_requirements_not_met' );
		} else {
			update_option( 'wpo_wcpdf_cloud_service_requirements_not_met', true );
			$this->cloud_api_class::log( 'error', 'API requirements not met: service_requirements_met()' );
		}
		
		return $requirements_met;
	}
	
	/**
	 * Get service options for settings
	 *
	 * @return array
	 */
	public function get_service_options(): array {
		$cloud_services = array();
		
		foreach ( self::available_services() as $slug => $service ) {
			$cloud_services[ $slug ] = $service['name'];
		}
		
		return $cloud_services;
	}
	
	/**
	 * Get the current service from the settings
	 *
	 * @return array
	 */
	public static function get_service_enabled(): array {
		$service_settings   = self::get_latest_settings();
		$available_services = self::available_services();
		
		if ( ! empty( $service_settings ) && ! empty( $service_settings['cloud_service'] ) && isset( $available_services[ $service_settings['cloud_service'] ] ) ) {
			return array( $service_settings['cloud_service'] => $available_services[ $service_settings['cloud_service'] ] );
		} else {
			return array();
		}
	}
	
	/**
	 * Get the latest settings
	 *
	 * @return array
	 */
	public static function get_latest_settings(): array {
		$settings = get_option( self::$settings_option, array() );

		if ( ! is_array( $settings ) ) {
			$settings = array();
		}

		return $settings;
	}
	
	/**
	 * Add Cloud Storage settings tab to the PDF Invoice settings page
	 * @param  array $tabs slug => Title
	 * @return array $tabs with Cloud Storage
	 */
	public function settings_tab( array $tabs ): array {
		$tabs['cloud_storage'] = __( 'Cloud storage', 'wpo_wcpdf_pro' );
		return $tabs;
	}

	/**
	 * General cloud storage settings define by the user
	 * 
	 * @return	void
	 */
	public function init_settings(): void {
		$this->maybe_init_cloud_api();
		
		$page = $option_group = $option_name = self::$settings_option;
		
		$settings_fields = array(
			array(
				'type'     => 'section',
				'id'       => 'cloud_storage_general_settings',
				'title'    => '',
				'callback' => 'section',
			),
			array(
				'type'     => 'setting',
				'id'       => 'enabled',
				'title'    => __( 'Enable', 'wpo_wcpdf_pro' ),
				'callback' => 'checkbox',
				'section'  => 'cloud_storage_general_settings',
				'args'     => array(
					'option_name' => $option_name,
					'id'          => 'enabled',
				),
			),
			array(
				'type'     => 'setting',
				'id'       => 'cloud_service',
				'title'    => __( 'Service', 'wpo_wcpdf_pro' ),
				'callback' => 'select',
				'section'  => 'cloud_storage_general_settings',
				'args'     => array(
					'option_name' => $option_name,
					'id'          => 'cloud_service',
					'options'     => $this->get_service_options(),
				),
			),
		);
		
		// Service specific settings
		foreach ( self::available_services() as $slug => $service ) {
			if ( ! empty( $service['settings'] ) && is_array( $service['settings'] ) ) {
				$settings_fields = array_merge( $settings_fields, $service['settings'] );
			}
		}
		
		// Extra settings
		$extra_settings = array(
			array(
				'type'     => 'setting',
				'id'       => 'auto_upload',
				'title'    => __( 'Auto-upload attachments', 'wpo_wcpdf_pro' ),
				'callback' => 'checkbox',
				'section'  => 'cloud_storage_general_settings',
				'args'     => array(
					'option_name' => $option_name,
					'id'          => 'auto_upload',
					'description' => __( 'Automatically upload all email attachments to your connected service.', 'wpo_wcpdf_pro' ),
				),
			),
			array(
				'type'     => 'setting',
				'id'       => 'year_month_folders',
				'title'    => __( 'Organize uploads', 'wpo_wcpdf_pro' ),
				'callback' => 'checkbox',
				'section'  => 'cloud_storage_general_settings',
				'args'     => array(
					'option_name' => $option_name,
					'id'          => 'year_month_folders',
					'description' => __( 'Creates subfolders based on the current year and month (e.g. 2024/06)', 'wpo_wcpdf_pro' ),
				),
			),
			array(
				'type'     => 'setting',
				'id'       => 'api_log',
				'title'    => __( 'Debug logging', 'wpo_wcpdf_pro' ),
				'callback' => 'checkbox',
				'section'  => 'cloud_storage_general_settings',
				'args'     => array(
					'option_name' => $option_name,
					'id'          => 'api_log',
					'description' => __( 'Enable this option to log events for debugging. ', 'wpo_wcpdf_pro' )
						. '<a href="' . esc_url_raw( admin_url( 'admin.php?page=wc-status&tab=logs' ) ) . '" target="_blank">'
						. __( 'View logs', 'wpo_wcpdf_pro' ) . '</a>',
				),
			),
			array(
				'type'     => 'section',
				'id'       => 'cloud_storage_upload_by_order_status',
				'title'    => '',
				'callback' => 'section',
			),
			array(
				'type'     => 'setting',
				'id'       => 'pro_notice_cloud_storage_upload_by_order_status',
				'title' => __( 'Upload on order status change', 'wpo_wcpdf_pro' ),
				'callback' => 'html_section',
				'section'  => 'cloud_storage_upload_by_order_status',
				'args'     => array(
					'option_name' => $option_name,
					'id'          => 'pro_notice_cloud_storage_upload_by_order_status',
					'class'       => 'doc-order-status',
					'html'        => '<span style="font-size:9pt;color:#646970;">' . 
						esc_html__( 'Uploads are triggered automatically when the order status changes. If you are already emailing the documents, leave these settings empty to avoid slowing down your site.', 'wpo_wcpdf_pro' ) .
					'</span>',
				),
			),
		);
		
		$settings_fields = array_merge( $settings_fields, $extra_settings );
		
		// Add per status upload settings
		foreach ( WPO_WCPDF_Pro()->functions->get_document_type_options() as $option ) {
			$settings_fields = array_merge( $settings_fields, array(
				array(
					'type'     => 'setting',
					'id'       => "per_status_upload_{$option['value']}",
					'title'    => '',
					'callback' => 'select',
					'section'  => 'cloud_storage_upload_by_order_status',
					'args'     => array(
						'title'            => $option['title'],
						'option_name'      => $option_name,
						'id'               => "per_status_upload_{$option['value']}",
						'options_callback' => 'wc_get_order_statuses',
						'multiple'         => true,
						'enhanced_select'  => true,
						'placeholder'      => __( 'Select one or more statuses', 'wpo_wcpdf_pro' ),
						'class'            => 'doc-order-status',
					),
				),
			) );
		}
	
		// allow plugins to alter settings fields
		$settings_fields = apply_filters( 'wpo_wcpdf_settings_fields_cloud_storage', $settings_fields, $page, $option_group, $option_name );
		WPO_WCPDF()->settings->add_settings_fields( $settings_fields, $page, $option_group, $option_name );
	}

	/**
	 * Button to unlink cloud service account
	 * 
	 * @param string $tab
	 * 
	 * @return void
	 */
	public function unlink( string $tab ): void {
		if ( ! $this->cloud_api_class::is_enabled() ) {
			return;
		}
		
		$delete_token_key = 'wpo_wcpdf_unlink_' . $this->cloud_api_class::$service_slug;

		// Remove API details if requested.
		if ( isset( $_REQUEST[ $delete_token_key ] ) && check_admin_referer( 'generate_wpo_wcpdf', '_wpnonce' ) ) {
			$this->cloud_api_class::unlink_service_settings( isset( $_REQUEST[ $delete_token_key ] ) && wc_string_to_bool( sanitize_key( $_REQUEST[ $delete_token_key ] ) ) );
			wp_redirect( esc_url_raw( remove_query_arg( $delete_token_key ) ) );
			exit();
		}

		// Display unlink button if we have an access token.
		$service_api_settings = get_option( 'wpo_wcpdf_' . $this->cloud_api_class::$service_slug . '_api_settings' );

		if ( 'cloud_storage' === $tab && isset( $service_api_settings['access_token'] ) ) {
			$service_logo           = $this->cloud_api_class::$service_logo;
			$service_name           = $this->cloud_api_class::$service_name;
			$service_slug           = $this->cloud_api_class::$service_slug;
			$unlink_url             = wp_nonce_url( add_query_arg( array( $delete_token_key => 'true' ), admin_url( 'admin.php?page=wpo_wcpdf_options_page&tab=cloud_storage' ) ), 'generate_wpo_wcpdf' );
			$refresh_token_ajax_url = esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'wpo_wcpdf_cloud_refresh_token', 'service' => $this->cloud_api_class::$service_slug, 'redirect' => 'yes' ), admin_url( 'admin-ajax.php' ) ), 'generate_wpo_wcpdf' ) );
			$last_settings          = self::get_latest_settings();
			$destination_prefix     = '';
			
			$destination_folder_raw = ! empty( $last_settings[ $service_slug . '_destination_folder' ] )
				? $last_settings[ $service_slug . '_destination_folder' ]
				: 'PDF Invoices & Packing Slips for WooCommerce';

			$destination_folder     = str_replace( '/', ' &rarr; ', $destination_folder_raw );
			
			switch ( $service_slug ) {
				case 'dropbox':
					if ( isset( $last_settings['dropbox_access_type'] ) && 'app_folder' === $last_settings['dropbox_access_type'] ) {
						$destination_prefix = __( 'All files', 'wpo_wcpdf_pro' ) . ' &rarr; ' . __( 'Apps', 'wpo_wcpdf_pro' ) . ' &rarr; ';
					} else {
						$destination_prefix = __( 'All files', 'wpo_wcpdf_pro' ) . ' &rarr; ';
					}
					break;
				case 'gdrive':
					$destination_prefix = __( 'My Drive', 'wpo_wcpdf_pro' ) . ' &rarr; ';
					break;
			}
			
			include_once WPO_WCPDF_Pro()->plugin_path() . '/includes/views/cloud-service-connection.php';
		}
	}

	/**
	 * Get a list of WooCommerce order statuses (without the wc- prefix)
	 *
	 * @return  array status slug => status name
	 */
	public function get_order_statuses(): array {
		$statuses = wc_get_order_statuses();
		
		foreach ( $statuses as $status_slug => $status ) {
			$status_slug                    = 'wc-' === substr( $status_slug, 0, 3 ) ? substr( $status_slug, 3 ) : $status_slug;
			$order_statuses[ $status_slug ] = $status;
		}

		return $order_statuses;
	}
	
	/**
	 * Check if we need to unlink the cloud service
	 *
	 * @param array $value           The sanitized option value.
	 * @param string $option         The option name.
	 * @param string|null $original_value The original value passed to the function. (since WP4.3)
	 *
	 * @return array
	 */
	public function maybe_unlink_service( array $value, string $option, ?string $original_value = null ): array {
		// get general settings
		$last_settings = self::get_latest_settings();

		// supported access type keys
		$access_type_keys = [ 'dropbox_access_type', 'gdrive_access_type' ];

		foreach ( $access_type_keys as $key ) {
			$last_access_type = isset( $last_settings[ $key ] ) ? $last_settings[ $key ] : null;
			$new_access_type  = isset( $value[ $key ] )         ? $value[ $key ]         : null;

			if ( $last_access_type !== $new_access_type && isset( $last_settings['cloud_service'] ) ) {
				delete_option( 'wpo_wcpdf_' . $last_settings['cloud_service'] . '_api_settings' );
				break; // only need to unlink once
			}
		}

		return $value;
	}

	/**
	 * Upload PDF to cloud service during/after email attachment
	 * 
	 * @param string $filepath
	 * @param string $document_type
	 * @param object|null $document
	 * 
	 * @return	void
	 */
	public function upload_attachment( string $filepath, string $document_type = '', ?object $document = null ): void {
		$cloud_service_slug = $this->cloud_api_class::$service_slug;
		
		// check if we have a cloud service
		if ( empty( $cloud_service_slug ) ) {
			return;
		}

		// get service api settings
		$service_settings = self::get_latest_settings();
		
		// check if upload enabled
		if ( ! isset( $service_settings['auto_upload'] ) || $service_settings['auto_upload'] == 0 || ! $this->cloud_api_class::is_enabled() ) {
			return;
		}

		if ( ! empty( $document ) && ! empty( $document->order ) ) {
			$this->upload_to_service( $filepath, 'attachment', $document->order, $document->get_type() );
		} else {
			$this->upload_to_service( $filepath, 'attachment', null, null );			
		}
	}

	/**
	 * Upload PDF to cloud service during/after email attachment
	 * 
	 * @param string $order_id
	 * @param string $old_status
	 * @param string $new_status
	 * @param \WC_Abstract_Order|null $order
	 * 
	 * @return	void
	 */
	public function upload_by_status( int $order_id, string $old_status, string $new_status, ?\WC_Abstract_Order $order ) {
		$cloud_service_slug = $this->cloud_api_class::$service_slug;
		
		// check if we have a cloud service
		if ( empty( $cloud_service_slug ) ) {
			return;
		}

		// get service api settings
		$service_settings           = self::get_latest_settings();
		$per_status_upload_settings = array();
		
		foreach ( $service_settings as $setting_key => $setting_value ) {
			if ( false !== strpos( $setting_key, 'per_status_upload_' ) ) {
				$document_type                                = str_replace( 'per_status_upload_', '', $setting_key );
				$per_status_upload_settings[ $document_type ] = $setting_value;
			}
		}
		
		// check if upload enabled
		if ( empty( $per_status_upload_settings ) || ! $this->cloud_api_class::is_enabled() ) {
			return;
		}

		foreach ( $per_status_upload_settings as $document_type => $upload_statuses ) {
			if ( empty( $upload_statuses ) ) {
				continue;
			}
			
			if ( false !== strpos( $document_type, '_xml' ) ) {
				$document_type = str_replace( '_xml', '', $document_type );
				$output_format = 'xml';
			} else {
				$output_format = 'pdf';
			}
			
			foreach ( $upload_statuses as $upload_status ) {
				$upload_status = str_replace( 'wc-', '', $upload_status );
				
				// check if new status matches upload status for document
				if ( $new_status === $upload_status ) {
					// check if free order + free invoice disabled
					$document_settings = WPO_WCPDF()->settings->get_document_settings( $document_type, $output_format );
					$free_disabled     = isset( $document_settings['disable_free'] );

					if ( $free_disabled && $order && $order->get_total() == 0 ) {
						continue;
					}

					// hook into upload by status
					do_action( 'wpo_wcpdf_cloud_storage_upload_by_status', $document_type, $order, $new_status, $old_status, $output_format );

					// upload file to cloud service
					$upload_response = $this->upload_document( $document_type, $order, 'status', $output_format );
					
					if ( ! $upload_response ) {
						$this->cloud_api_class::log( 'error', 'upload failed for order ID #' . $order_id . ', document type ' . $document_type . ' and context upload_by_status().' );
					}
				}
			}
		}
	}

	/**
	 * Create and upload PDF document to cloud service
	 * 
	 * @param string $document_type
	 * @param object|null $order
	 * @param string $context
	 * @param string $output_format
	 * 
	 * @return array|false|void
	 */
	public function upload_document( string $document_type, ?object $order, string $context = 'action_hook', string $output_format = 'pdf' ) {
		if ( ! $order ) {
			$this->cloud_api_class::log( 'error', 'Invalid order object' );
			return false;
		}
		
		// NOTE:
		// we use ID to force to reloading the order to make sure that all meta data is up to date.
		// this is especially important when multiple emails with the PDF document are sent in the same session
		$document = wcpdf_get_document( $document_type, (array) $order->get_id(), true );
				
		if ( ! $document || ! $document->exists() ) {
			$this->cloud_api_class::log( 'error', 'Couldn\'t get or create document for upload' );
			return false;
		}
		
		try {
			$filepath = wcpdf_get_document_file( $document, $output_format );
		} catch ( \Exception $e ) {
			wcpdf_log_error( $e->getMessage(), 'critical', $e );
			return false;
		} catch ( \Dompdf\Exception $e ) {
			wcpdf_log_error( 'DOMPDF exception: ' . $e->getMessage(), 'critical', $e );
			return false;
		} catch ( \Throwable $e ) {
			wcpdf_log_error( $e->getMessage(), 'critical', $e );
			return false;
		}
		
		if ( empty( $filepath ) && ! file_exists( $filepath ) ) {
			$this->cloud_api_class::log( 'error', 'File does not exist!' );
			return false;
		}

		// upload file to cloud service
		return $this->upload_to_service( $filepath, $context, $order, $document_type );
	}

	/**
	 * Upload file to cloud service
	 * 
	 * @param string $filepath
	 * 
	 * @return void
	 */
	public function upload_file( string $filepath ): void {
		if ( empty( $filepath ) ) {
			return;
		}

		// upload file to cloud service
		$this->upload_to_service( $filepath, 'upload_file', null, null );
	}

	/**
	 * Upload file to cloud service
	 * 
	 * @param string $filepath
	 * @param string $context
	 * @param \WC_Abstract_Order|null $order
	 * @param string|null $document_type
	 * 
	 * @return array|void
	 */
	public function upload_to_service( string $filepath, string $context = 'attachment', ?\WC_Abstract_Order $order = null, ?string $document_type = null ) {
		// check if enabled
		if ( $this->cloud_api_class::is_enabled() === false ) {
			return;
		}

		// check if we have a cloud service
		if ( empty( $cloud_service_slug = $this->cloud_api_class::$service_slug ) ) {
			return;
		}

		// custom hook to allow/stop the file upload
		$allow_upload = apply_filters( 'wpo_wcpdf_custom_cloud_service_allow_upload', true, $filepath, $order, $document_type, $cloud_service_slug );

		if ( ! empty( $filepath ) && file_exists( $filepath ) && $allow_upload === true ) {

			$this->cloud_api_class::log( 'info', 'Upload to '.$cloud_service_slug.' initiated' );
			
			$result = $this->upload_service_selection( $filepath, $order, $document_type );
			
			if ( isset( $result['error'] ) ) {
				$this->cloud_api_class::log( 'error', "{$cloud_service_slug} upload permission denied" );
				
				// there was an error uploading the file, copy file to queue
				$this->queue_file( $filepath, $order, $document_type );
				
				return array( 'error' => __( 'Cloud service upload permission denied', 'wpo_wcpdf_pro' ) );
			} else {
				// unless we are uploading attachments (which are needed in other processes), delete the file after uploading
				if ( apply_filters( 'wpo_wcpdf_cloud_delete_temporary_file_after_upload', ( ! in_array( $context, array( 'attachment', 'upload_file' ) ) ), $filepath, $context, $document_type ) ) {
					@unlink( $filepath );
				}
				return $result;
			}
			
		} else {
			$this->cloud_api_class::log( 'error', 'file does not exist!' );
			return array( 'error' => __( 'File does not exist', 'wpo_wcpdf_pro' ) );
		}
	}

	/**
	 * Selects the correct cloud service to upload
	 * 
	 * @param string $filepath
	 * @param \WC_Abstract_Order|null $order
	 * @param string|null $document_type
	 * 
	 * @return array|false
	 */
	public function upload_service_selection( string $filepath, ?\WC_Abstract_Order $order, ?string $document_type ) {
		$service_slug = $this->cloud_api_class::$service_slug;
		
		if ( ! $this->service_requirements_met( $service_slug ) ) {
			return false;
		}
		
		switch ( $service_slug ) {
			case 'dropbox':
				$api = new \WPO\WC\PDF_Invoices_Pro\Cloud\Dropbox\Dropbox_API;
				break;
			case 'ftp':
				$api = new \WPO\WC\PDF_Invoices_Pro\Cloud\FTP\FTP_Upload;
				break;
			case 'gdrive':
				$api = new \WPO\WC\PDF_Invoices_Pro\Cloud\Gdrive\Gdrive_API;
				break;
			default:
				$api = false;
				break;
		}
		
		if ( ! $api ) {
			$this->cloud_api_class::log( 'error', 'API not found: upload_service_selection()' );
			return false;
		}

		return $api->upload( $filepath, $order, $document_type );
	}
	
	/**
	 * Display a notice if the service requirements are not met
	 * 
	 * @return void
	 */
	public function service_requirements_not_met_notice(): void {
		if ( get_option( 'wpo_wcpdf_cloud_service_requirements_not_met' ) ) {
			?>
			<div class="notice notice-error">
				<p><?php printf(
					/* translators: %1$s: opening a tag, %2$s: closing a tag */
					__( 'The requirements for the selected cloud service are not met. Please check the cloud storage requirements %1$shere%2$s.', 'wpo_wcpdf_pro' ),
					'<a href="' . esc_url( admin_url( 'admin.php?page=wpo_wcpdf_options_page&tab=debug&section=status' ) ) . '" target="_blank">',
					'</a>'
				);
				?></p>
				<p><a href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wpo_wcpdf_hide_cloud_service_requirements_not_met_notice', 'true' ), 'hide_cloud_service_requirements_not_met_notice' ) ); ?>"><?php _e( 'Hide this message', 'wpo_wcpdf_pro' ); ?></a></p>
			</div>
			<?php
		}

		if ( isset( $_REQUEST['wpo_wcpdf_hide_cloud_service_requirements_not_met_notice'] ) && isset( $_REQUEST['_wpnonce'] ) ) {
			if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'hide_cloud_service_requirements_not_met_notice' ) ) {
				wcpdf_log_error( 'You do not have sufficient permissions to perform this action: wpo_wcpdf_hide_cloud_service_requirements_not_met_notice' );
			} else {
				delete_option( 'wpo_wcpdf_cloud_service_requirements_not_met' );
			}
			
			wp_redirect( esc_url_raw( admin_url( 'admin.php?page=wpo_wcpdf_options_page&tab=cloud_storage' ) ) );
			exit;
		}
	}

	/**
	 * Export PDFs in bulk from the order actions drop down
	 * 
	 * @return void
	 */
	public function bulk_export(): void {
		// check if enabled
		if ( ! $this->cloud_api_class::is_enabled() ) {
			return;
		}
		
		if ( isset( $_GET['post_type'] ) && 'shop_order' === wp_unslash( $_GET['post_type'] ) ) {
			
			// Check if all parameters are set
			if ( ( empty( $_GET['order_ids'] ) && empty( $_REQUEST['post'] ) ) || empty( $_GET['action'] ) ) {
				return;
			}

			// Check the user privileges
			if ( ! current_user_can( 'manage_woocommerce_orders' ) && ! current_user_can( 'edit_shop_orders' ) && ! isset( $_GET['my-account'] ) ) {
				return;
			}
			
			// convert order_ids to array if set
			if ( isset( $_GET['order_ids'] ) ) {
				$order_ids = (array) explode( 'x', $_GET['order_ids'] );
			} else {
				$order_ids = (array) $_REQUEST['post'];
			}
			
			if ( empty( $order_ids ) ) {
				return;
			}

			// Process oldest first: reverse $order_ids array
			$order_ids = array_reverse( $order_ids );
			
			// get the action
			$wp_list_table        = _get_list_table( 'WP_Posts_List_Table' );
			$action               = $wp_list_table->current_action();
			$export_action_prefix = 'wpo_wcpdf_cloud_service_export_';

			if ( 'wpo_wcpdf_cloud_service_export_process' === $action ) {
				$document_type = esc_attr( $_GET['template'] );
				$this->bulk_export_process( $order_ids, $document_type );
			} elseif ( ! empty( $export_action_prefix ) && ! empty( $action ) && false !== strpos( $action, $export_action_prefix ) ) {
				$document_type = str_replace( $export_action_prefix, '', $action );
				$this->bulk_export_page( $order_ids, $document_type );
			} else {
				return;
			}

			exit();
		}
	}

	/**
	 * Process export queue
	 * 
	 * @return void
	 */
	public function export_queue(): void {
		// check if enabled
		if ( ! $this->cloud_api_class::is_enabled() ) {
			return;
		}

		if ( isset( $_GET['post_type'] ) && 'shop_order' === wp_unslash( $_GET['post_type'] ) ) {
			$action        = isset( $_GET['action'] ) && ! empty( $_GET['action'] ) ? str_replace( 'wpo_wcpdf_cloud_service_', '', esc_attr( $_GET['action'] ) ) : '';
			$valid_actions = array(
				'upload_queue',
				'clear_queue',
				'queue_process',
			);

			// Check action
			if ( ! in_array( $action, $valid_actions ) ) {
				return;
			}

			// Check the user privileges
			if ( ! current_user_can( 'manage_woocommerce_orders' ) && ! current_user_can( 'edit_shop_orders' ) && ! isset( $_GET['my-account'] ) ) {
				return;
			}
			
			switch ( $action ) {
				case 'upload_queue':
					$this->queue_page( 'upload' );
					break;
				case 'clear_queue':
					$this->queue_page( 'clear' );
					break;
				case 'queue_process':
					$do = isset( $_GET['do'] ) ? esc_attr( $_GET['do'] ) : '';
					$this->queue_process( $do );
					break;
				default:
					return;
			}

			exit();
		}
	}

	/**
	 * Displays the queue notification modal
	 * 
	 * @param string $do
	 * 
	 * @return void
	 */
	public function queue_page( string $do ): void {
		// create url/path to process page
		$action_args = array (
			'action' => 'wpo_wcpdf_cloud_service_queue_process',
			'do'     => $do,
		);

		$new_page = add_query_arg( $action_args, remove_query_arg( 'action' ) );

		// render pre-export page (waiting page with spinner)
		if ( 'upload' === $do ) {
			$message = sprintf(
				/* translators: service name */
				__( 'Please wait while your queued PDF documents are being uploaded to %s...', 'wpo_wcpdf_pro' ),
				$this->cloud_api_class::$service_name
			);
		} else {
			$message = __( 'Please wait while the upload queue is being cleared', 'wpo_wcpdf_pro' );
		}

		$service_name = $this->cloud_api_class::$service_name;
		$plugin_url   = WPO_WCPDF_Pro()->plugin_url();
		
		include WPO_WCPDF_Pro()->plugin_path().'/includes/cloud/templates/template-bulk-export-page.php';
	}

	/**
	 * Displays the bulk export notification modal
	 * 
	 * @param array  $order_ids
	 * @param string $document_type
	 * 
	 * @return void
	 */
	public function bulk_export_page( array $order_ids, string $document_type ): void {
		// create url/path to process page
		$action_args = array (
			'action'	=> 'wpo_wcpdf_cloud_service_export_process',
			'template'	=> $document_type,
		);

		// render pre-export page (waiting page with spinner)
		/* translators: service name */
		$message      = sprintf( __( 'Please wait while your PDF documents are being uploaded to %s...', 'wpo_wcpdf_pro' ), $this->cloud_api_class::$service_name );
		$new_page     = add_query_arg( $action_args, remove_query_arg( 'action' ) );
		$service_name = $this->cloud_api_class::$service_name;
		$plugin_url   = WPO_WCPDF_Pro()->plugin_url();

		include WPO_WCPDF_Pro()->plugin_path().'/includes/cloud/templates/template-bulk-export-page.php';
	}		

	/**
	 * Bulk export process
	 * 
	 * @param array  $order_ids
	 * @param string $document_type
	 * 
	 * @return void
	 */
	public function bulk_export_process( array $order_ids, string $document_type ): void {
		foreach ( $order_ids as $order_id ) {
			if ( false !== strpos( $document_type, '_xml' ) ) {
				$_document_type = str_replace( '_xml', '', $document_type );
				$output_format  = 'xml';
			} else {
				$_document_type = $document_type;
				$output_format  = 'pdf';
			}
			
			$order = wc_get_order( absint( $order_id ) );
			
			if ( ! $order ) {
				$this->cloud_api_class::log( 'error', 'invalid order object: #' . $order_id );
				continue;
			}
			
			$upload_response = $this->upload_document( $_document_type, $order, 'export', $output_format );
			
			if ( ! $upload_response ) {
				$error_message = 'upload failed for order ID #' . $order_id . ', document type ' . $document_type . ' and context bulk_export_process().';
				$this->cloud_api_class::log( 'error', $error_message );
				$errors[ $order_id ] = $error_message;
				continue;
			}

			if ( ! empty( $upload_response['error'] ) ) {
				// Houston, we have a problem
				$errors[ $order_id ] = $upload_response['error'];
			}
		}

		// render export done page
		if ( isset( $errors ) ) {
			$service_api_settings = get_option( 'wpo_wcpdf_' . $this->cloud_api_class::$service_slug . '_api_settings' );
			
			$message  = sprintf(
				/* translators: cloud service name */
				__( 'There were errors when trying to upload to %s.', 'wpo_wcpdf_pro' ),
				$this->cloud_api_class::$service_name
			) .'<br><br>';
			
			// mention pro license
			if ( 'valid' !== WPO_WCPDF_Pro()->functions->get_pro_license_status() ) {
				$message .= __( 'Your Professional extension license is not valid or is inactive.', 'wpo_wcpdf_pro' );
				
			// mention logs
			} else {
				if ( ! isset( $service_api_settings['api_log'] ) ) {
					$message  .= sprintf(
						/* translators: here link */
						__( 'Please enable the service logging %s to be able to debug the issue.', 'wpo_wcpdf_pro' ),
						'<a href="' . esc_url_raw( admin_url( 'admin.php?page=wpo_wcpdf_options_page&tab=cloud_storage' ) ) . '" target="_blank">' . __( 'here', 'wpo_wcpdf_pro' ) . '</a>'
						);
				} else {
					$view_log  = '<a href="' . esc_url_raw( admin_url( 'admin.php?page=wc-status&tab=logs' ) ) . '" target="_blank">'.__( 'View logs', 'wpo_wcpdf_pro' ).'</a>';
					$message  .= __( 'Check the error log for details:', 'wpo_wcpdf_pro' ) . '<br>' . $view_log;
				}
			}
			
		} else {
			$message = sprintf(
				/* translators: cloud service name */
				__( 'Documents successfully uploaded to %s!', 'wpo_wcpdf_pro' ),
				$this->cloud_api_class::$service_name
			);
		}

		$service_name = $this->cloud_api_class::$service_name;
		$plugin_url   = WPO_WCPDF_Pro()->plugin_url();

		include WPO_WCPDF_Pro()->plugin_path() . '/includes/cloud/templates/template-bulk-export-process.php';		
	}

	/**
	 * Adds PDF file to queue
	 * 
	 * @param string $filepath
	 * @param \WC_Abstract_Order|null $order
	 * @param string|null $document_type
	 * 
	 * @return void
	 */
	public function queue_file( string $filepath, ?\WC_Abstract_Order $order = null, ?string $document_type = null ) {
		$queue_folder = $this->get_queue_path();
		$filename     = basename( $filepath );
		$queue_file   = $queue_folder . $filename;
		
		copy( $filepath, $queue_file );

		// store order reference in db if available
		if ( ! empty( $order ) && is_object( $order ) ) {
			$cloud_service_queue = get_option( 'wpo_wcpdf_' . $this->cloud_api_class::$service_slug . '_queue', array() );
			
			if ( ! isset( $cloud_service_queue[ $queue_file ] ) ) {
				$order_id                           = method_exists( $order, 'get_id' ) ? $order->get_id(): $order->id;
				$cloud_service_queue[ $queue_file ] = array(
					'order_id'      => $order_id,
					'document_type' => $document_type,
				);
				update_option( 'wpo_wcpdf_' . $this->cloud_api_class::$service_slug . '_queue', $cloud_service_queue );
			}
		}

		$this->cloud_api_class::log( 'info', "file placed in queue: {$queue_file}" );
	}

	/**
	 * Gets the queue path
	 * 
	 * @return string|void
	 */
	public function get_queue_path() {
		if ( ! function_exists( 'WPO_WCPDF' ) && empty( WPO_WCPDF()->main ) ) {
			return;
		} 

		$queue_path = trailingslashit( WPO_WCPDF()->main->get_tmp_path( $this->cloud_api_class::$service_slug ) );

		// make sure the queue path is protected!
		// create .htaccess file and empty index.php to protect in case an open webfolder is used!
		if ( ! file_exists( $queue_path . '.htaccess' ) || ! file_exists( $queue_path . 'index.php' ) ) {
			@file_put_contents( $queue_path . '.htaccess', 'deny from all' );
			@touch( $queue_path . 'index.php' );
		}
		
		return $queue_path;
	}

	/**
	 * Gets the queued files
	 * 
	 * @param string $value
	 * 
	 * @return array|bool
	 */
	public function get_queued_files( string $value = '' ) {
		// get list of all files in the queue folder
		$queue_folder = $this->get_queue_path();
		$queued_files = scandir( $queue_folder );
		$queued_files = ! $queued_files ? [] : $queued_files;
		
		// remove . & ..
		$queued_files = array_diff( $queued_files, array( '.', '..', '.htaccess', 'index.php', '.DS_Store' ) );

		if ( ! count( $queued_files ) > 0 ) {
			// no files in queue;
			return false;
		} else {
			return $queued_files;
		}
	}

	/**
	 * Process to upload and clear queue
	 * 
	 * @param string $do
	 * 
	 * @return void
	 */
	public function queue_process( string $do ): void {
		// check if enabled
		if ( ! $this->cloud_api_class::is_enabled() || empty( $do ) ) {
			return;
		}

	 	switch ( $do ) {
	 		case 'upload':
				$queued_files = $this->get_queued_files();
				
				if ( ! empty( $queued_files ) ) {
					$cloud_service_queue = get_option( 'wpo_wcpdf_' . $this->cloud_api_class::$service_slug . '_queue', array() );
					
					foreach ( $queued_files as $queued_file ) {
						$file_path     = $this->get_queue_path() . $queued_file;
						$document_type = null;
						$order         = null;

						// load order if we have stored it
						if ( ! empty( $cloud_service_queue[ $file_path ] ) && is_array( $cloud_service_queue[ $file_path ] ) ) {
							$document_type = $cloud_service_queue[ $file_path ]['document_type'] ?? null;
							$order_id      = (int) ( $cloud_service_queue[ $file_path ]['order_id'] ?? 0 );
							$order         = $order_id > 0 ? wc_get_order( $order_id ) : null;
							$order         = $order instanceof \WC_Abstract_Order ? $order : null;
						}
						
						// upload file to cloud service
						$upload_response = $this->upload_to_service( $file_path, 'export', $order, $document_type );

						if ( ! empty( $upload_response['error'] ) ) {
							// Houston, we have a problem
							$errors[] = $upload_response['error'];
							
							$this->cloud_api_class::log( 'error', $upload_response['error'] );
							
						} else {
							// remove file
							if ( file_exists( $file_path ) ) {
								unlink( $file_path );
							}
							
							// and from queue reference
							if ( isset( $cloud_service_queue[ $file_path ] ) ) {
								unset( $cloud_service_queue[ $file_path ] );
								update_option( 'wpo_wcpdf_' . $this->cloud_api_class::$service_slug . '_queue', $cloud_service_queue );
							}
						}
					}						
				}
	 			break;
	 		case 'clear':
	 			// delete all pdf files from queue folder
	 			$queue_path     = $this->get_queue_path();
				$output_formats = array( 'pdf', 'ubl', 'xml' );
				
				foreach ( $output_formats as $output_format ) {
					array_map( 'unlink', ( glob( $queue_path . '*' . wcpdf_get_document_output_format_extension( $output_format ) ) ? glob( $queue_path . '*' . wcpdf_get_document_output_format_extension( $output_format ) ) : array() ) );
				}
				
				// delete queue option
				delete_option( 'wpo_wcpdf_'.$this->cloud_api_class::$service_slug.'_queue' );
	 			break;
	 	}

		// render export done page
		if ( isset( $errors ) ) {
			$service_api_settings = get_option( 'wpo_wcpdf_' . $this->cloud_api_class::$service_slug . '_api_settings' );
			$message              = sprintf( __( 'There were errors when trying to upload to %s.', 'wpo_wcpdf_pro' ), $this->cloud_api_class::$service_name ) .'<br><br>';
			
			// mention pro license
			if ( 'valid' !== WPO_WCPDF_Pro()->functions->get_pro_license_status() ) {
				$message .= __( 'Your Professional extension license is not valid or is inactive.', 'wpo_wcpdf_pro' );
				
			// mention logs
			} else {
				if ( ! isset( $service_api_settings['api_log'] ) ) {
					$message .= sprintf(
						/* translators: here link */
						__( 'Please enable the service logging %s to be able to debug the issue.', 'wpo_wcpdf_pro' ),
						'<a href="' . esc_url_raw( admin_url( 'admin.php?page=wpo_wcpdf_options_page&tab=cloud_storage' ) ) . '" target="_blank">' . __( 'here', 'wpo_wcpdf_pro' ) . '</a>'
						);
				} else {
					$view_log = '<a href="' . esc_url_raw( admin_url( 'admin.php?page=wc-status&tab=logs' ) ) . '" target="_blank">'.__( 'View logs', 'wpo_wcpdf_pro' ).'</a>';
					$message .= __( 'Check the error log for details:', 'wpo_wcpdf_pro' ) . '<br>' . $view_log;
				}
			}
			
		} elseif ( 'upload' === $do ) {
			$message = sprintf(
				/* translators: service name */
				__( 'PDF documents successfully uploaded to %s!', 'wpo_wcpdf_pro' ),
				$this->cloud_api_class::$service_name
			);
			
		} else {
			$message = __( 'Upload queue successfully cleared!', 'wpo_wcpdf_pro' );
		}

		$service_name = $this->cloud_api_class::$service_name;
		$plugin_url   = WPO_WCPDF_Pro()->plugin_url();

		include WPO_WCPDF_Pro()->plugin_path().'/includes/cloud/templates/template-bulk-export-process.php';			
	}

	/**
	 * Display notification about upload queue with link to process queue
	 * 
	 * @return void
	 */
	public function upload_queue(): void {
		$queue = $this->get_queued_files();

		$show_notice = apply_filters(
			'wpo_ips_pro_cloud_service_show_upload_queue_notice',
			true,
			$queue,
			$this
        );

		if ( ! empty( $queue ) && $this->cloud_api_class::is_enabled() && $show_notice ) {
			$files_count    = count ($queue );
			$upload_button	= '<a href="edit.php?post_type=shop_order&action=wpo_wcpdf_cloud_service_upload_queue" class="button-primary" id="cloud_service_upload_queue">' . __( 'Upload files', 'wpo_wcpdf_pro' ) . '</a>';
			$clear_button	= '<a href="edit.php?post_type=shop_order&action=wpo_wcpdf_cloud_service_clear_queue"  class="button-primary" id="cloud_service_clear_queue" >' . __( 'Clear queue', 'wpo_wcpdf_pro' ) . '</a>';
			?>
				<div class="updated">
					<p>
						<?php
							printf(
								/* translators: 1. files count, 2. service name */
								__( 'There are %1$s unfinished uploads in your the upload queue from PDF Invoices & Packing Slips for WooCommerce to %2$s.', 'wpo_wcpdf_pro' ),
								$files_count,
								$this->cloud_api_class::$service_name
							);
						?>
					</p>
					<p><?php echo $upload_button . ' ' . $clear_button; ?></p>
				</div>
			<?php
		}
	}

	/**
	 * Add cloud service actions to bulk action drop down menu, WP3.5+
	 * 
	 * @param array $actions
	 * 
	 * @return array
	 */
	public function export_actions( array $actions ): array {
		if ( $this->cloud_api_class::is_enabled() ) {
			foreach ( WPO_WCPDF_Pro()->functions->get_document_type_options() as $option ) {
				$actions["wpo_wcpdf_cloud_service_export_{$option['value']}"] = sprintf(
					/* translators: 1. document title, 2. service name */
					__( '%1$s to %2$s', 'wpo_wcpdf_pro' ),
					$option['title'],
					$this->cloud_api_class::$service_name
				);
			}
		}
		return $actions;
	}

	/**
	 * Enqueue scripts
	 * 
	 * @return void
	 */
	public function enqueue_scripts(): void {
		$screen = get_current_screen();
		
		if ( ! is_null( $screen ) && in_array( $screen->id, array( 'shop_order', 'edit-shop_order', 'woocommerce_page_wc-orders' ) ) ) {
			wp_enqueue_script(
				'wcpdf-pro-cloud-storage-export',
				WPO_WCPDF_Pro()->plugin_url() . '/assets/js/pro-cloud-storage-export.js',
				array( 'jquery', 'thickbox' ),
				WPO_WCPDF_PRO_VERSION
			);
			
			wp_localize_script(
				'wcpdf-pro-cloud-storage-export',
				'wcpdf_pro_cloud_storage_export',
				array(
					'ajaxurl'                   => admin_url( 'admin-ajax.php' ),
					'nonce'                     => wp_create_nonce( 'pro_cloud_storage' ),
					'error_fetching_refund_ids' => __( 'Error fetching refund order IDs', 'wpo_wcpdf_pro' ),
					'error_no_refunds_found'    => __( 'No refunds found for this order', 'wpo_wcpdf_pro' ),
				)
			);
		}

		if ( $this->get_queued_files() ) {
			wp_enqueue_style( 'thickbox' );
			wp_enqueue_script(
				'wcpdf-pro-cloud-storage-queue',
				WPO_WCPDF_Pro()->plugin_url() . '/assets/js/pro-cloud-storage-queue.js',
				array( 'jquery', 'thickbox' ),
				WPO_WCPDF_PRO_VERSION
			);
		}

		wp_enqueue_style(
			'wcpdf-pro-cloud-storage-styles',
			WPO_WCPDF_Pro()->plugin_url() . '/assets/css/cloud-storage-styles.css',
			array(),
			WPO_WCPDF_PRO_VERSION
		);
				
		if ( 'woocommerce_page_wpo_wcpdf_options_page' === $screen->id && isset( $_GET['tab'] ) && 'cloud_storage' === $_GET['tab'] ) {
			wp_enqueue_script(
				'wcpdf-pro-cloud-storage-scripts',
				WPO_WCPDF_Pro()->plugin_url() . '/assets/js/pro-cloud-storage-scripts.js',
				array( 'jquery' ),
				WPO_WCPDF_PRO_VERSION
			);
			
			wp_localize_script(
				'wcpdf-pro-cloud-storage-scripts',
				'wcpdf_pro_cloud_storage_scripts',
				array(
					'are_account_credentials_set' => $this->cloud_api_class::are_account_credentials_set(),
					'unlink_required_message'     => __( 'To change this, you must first disconnect the currently linked account.', 'wpo_wcpdf_pro' ),
				)
			);
		}
	}
	
	/**
	 * Add manually refresh tokens setting to settings debug page
	 * 
	 * @param array $settings_fields
	 * @param string $page
	 * @param string $option_group
	 * @param string $option_name
	 * 
	 * @return	array
	 */
	public function manually_refresh_tokens_setting( array $settings_fields, string $page, string $option_group, string $option_name ): array {
		$new_setting = array(
			'type'     => 'setting',
			'id'       => 'manually_refresh_tokens',
			'title'    => __( 'Manually refresh cloud service tokens', 'wpo_wcpdf_pro' ),
			'callback' => 'checkbox',
			'section'  => 'debug_settings',
			'args'     => array(
				'option_name' => $option_name,
				'id'          => 'manually_refresh_tokens',
				'description' => __( 'Enable this option to manually refresh the authentication tokens for connected cloud services. This feature is intended for use during troubleshooting and debugging to ensure that your application maintains a valid connection with external cloud services.', 'wpo_wcpdf_pro' ),
			)
		);
		
		$danger_zone_key = null;

		foreach ( $settings_fields as $index => $setting_field ) {
			if ( ! empty( $setting_field['id'] ) && 'enable_danger_zone_tools' === $setting_field['id'] ) {
				$danger_zone_key = $index;
			}
		}

		if ( ! is_null( $danger_zone_key ) ) {
			// Insert the new array before the Danger zone.
			array_splice( $settings_fields, $danger_zone_key, 0, array( $new_setting ) );
		} else {
			// If Danger zone not found, just append to the end.
			$settings_fields[] = $new_setting;
		}

		return $settings_fields;
	}
	
	/**
	 * Add manually refresh tokens to the correct settings tab.
	 * 
	 * @param array $categories
	 * @return array
	 */
	public function manually_refresh_tokens_setting_category( array $settings_categories ): array {
		$settings_categories = WPO_WCPDF()->settings->add_single_setting_field_to_category(
			$settings_categories, 
			'manually_refresh_tokens', 
			'advanced_experimental',
			2
		);

		return apply_filters( 'wpo_wcpdf_pro_debug_settings_categories', $settings_categories );
	}
	
	/**
	 * Add Cloud Storage Requirements table to Advanced Status page
	 *
	 * @return void
	 */
	public function cloud_requirements_table(): void {
		$requirements = array();
		
		foreach ( self::available_services() as $service ) {
			if ( ! empty( $service['requirements']['php'] ) && ! empty( $service['name'] ) ) {
				$required_version = version_compare( $service['requirements']['php'], WPO_WCPDF_Pro()->dependencies->php_version, '<' )
					? WPO_WCPDF_Pro()->dependencies->php_version
					: $service['requirements']['php'];
				
				$requirements[ $service['name'] ] = array(
					'required' => 'PHP ' . $required_version,
					'value'    => PHP_VERSION,
					'result'   => version_compare( PHP_VERSION, $service['requirements']['php'], '>' ),
				);
			}
		}
		
		$requirements = apply_filters( 'wpo_wcpdf_pro_cloud_storage_requirements', $requirements );
		
		include_once WPO_WCPDF_Pro()->plugin_path() . '/includes/views/cloud-storage-requirements.php';
	}
	
	/**
	 * Show a one-time notice about the new Google Drive integration in Cloud Storage.
	 *
	 * @return void
	 */
	public function gdrive_announcement_notice(): void {
		$slug = 'wpo_ips_pro_dismiss_cloud_storage_gdrive_announcement';

		// Bail if already dismissed or user cannot manage settings.
		if ( wc_string_to_bool( get_option( $slug ) ) || ! WPO_WCPDF()->settings->user_can_manage_settings() ) {
			return;
		}

		// Handle dismissal.
		if ( isset( $_GET[ $slug ], $_GET['_wpnonce'] ) ) {
			if ( wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), $slug ) ) {
				update_option( $slug, 'yes' );
			} else {
				wcpdf_log_error( 'Invalid nonce while dismissing Cloud Storage Google Drive feature notice.' );
			}

			wp_safe_redirect( remove_query_arg( array( $slug, '_wpnonce' ) ) );
			exit;
		}
		?>
		<div class="notice notice-info">
			<p>
				<?php
				printf(
					/* translators: %1$s: Service name, %2$s: Plugin name */
					esc_html__(
						'We\'ve added %1$s support to Cloud Storage in %2$s.',
						'wpo_wcpdf_pro'
					),
					'<strong>Google Drive</strong>',
					'<strong>' . WPO_WCPDF_Pro()->plugin_name . '</strong>',
				);
				?>
			</p>
			<p>
				<?php
				printf(
					/* translators: %s: Service name */
					esc_html__( 'You can now connect your %s account and automatically upload your documents directly from the plugin.', 'wpo_wcpdf_pro' ),
					'Google Drive'
				);
				?>
			</p>
			<p>
				<a class="button button-primary" href="<?php echo esc_url( admin_url( 'admin.php?page=wpo_wcpdf_options_page&tab=cloud_storage' ) ); ?>">
					<?php
						printf(
							/* translators: %s: Service name */
							esc_html__( 'Configure %s', 'wpo_wcpdf_pro' ),
							'Google Drive'
						);
					?>
				</a>
				<a class="button" href="<?php echo esc_url( wp_nonce_url( add_query_arg( $slug, '1' ), $slug ) ); ?>">
					<?php esc_html_e( 'Dismiss this notice', 'wpo_wcpdf_pro' ); ?>
				</a>
			</p>
		</div>
		<?php
	}
	
	/**
	 * Register Cloud Storage setting type
	 * 
	 * @param array $setting_types
	 * @return array
	 */
	public function cloud_storage_setting_type( array $setting_types ): array {
		$setting_types['cloud_storage'] = __( 'Cloud Storage', 'wpo_wcpdf_pro' );
		return $setting_types;
	}
	
	/**
	 * Exported cloud storage settings
	 *
	 * @param array $settings
	 * @param string $type
	 * @return array
	 */
	public function cloud_storage_settings_export( array $settings, string $type ): array {
		if ( 'cloud_storage' === $type ) {
			$settings = self::get_latest_settings();
		}
		return $settings;
	}
	
	/**
	 * Get cloud storage settings option
	 *
	 * @param string $settings_option
	 * @param string $type
	 * @return string
	 */
	public function cloud_storage_settings_option( string $settings_option, string $type ): string {
		if ( 'cloud_storage' === $type ) {
			$settings_option = self::$settings_option;
		}
		return $settings_option;
	}
	
	/**
	 * Import cloud storage settings option
	 *
	 * @param string $settings_option
	 * @param string $type
	 * @param array  $new_settings
	 * @return string
	 */
	public function cloud_storage_settings_option_import( string $settings_option, string $type, array $new_settings ): string {
		return $this->cloud_storage_settings_option( $settings_option, $type );
	}
	
	/**
	 * Reset cloud storage settings option
	 *
	 * @param string $settings_option
	 * @param string $type
	 * @return string
	 */
	public function cloud_storage_settings_option_reset( string $settings_option, string $type ): string {
		return $this->cloud_storage_settings_option( $settings_option, $type );
	}

} // end class

endif; // end class_exists

return new Cloud_Storage();
