<?php
/**
 * Plugin Name:  Deploy & Sync Content
 * Description:  Retrieve content from your production site to your development instances.
 * Plugin URI:   https://wp-deployer.io/
 * Version:      2.2.0
 * Author:       EA Lab
 * Author URI:   https://ea-lab.io/
 * Text Domain:  deploy-sync-content
 * Domain Path:  /languages
 * Requires PHP: 7.4
 * License:      GPL v2 or later
 *
 * @link              https://wp-deployer.io/
 * @since             1.0.0-alpha1
 * @package EaLab\WP_Deployer
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
	die;
}

use EaLab\WP_Deployer\includes\deployerConfig\BasicConfigRepository;
use EaLab\WP_Deployer\vendor\Dakwamine\Component\ComponentBasedObject;
use EaLab\WP_Deployer\vendor\Dakwamine\Component\Exception\UnmetDependencyException;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Batch\BatchScheduler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\DeploymentManager;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Exception\InvalidConfigException;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Exception\WpDeployException;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Import\Importability;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\Config\ConfigProviderManagerBase;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\Config\Exclusions\ExclusionsConfigProviderManager;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\Config\TransmutationHandlerShortcodeReferences\TransmutationHandlerShortcodeReferencesConfigProviderManager;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\Config\ValueHandlerOption\ValueHandlerOptionConfigProviderManager;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemTracker\TrackDateInitializer\TrackDateInitializerManager;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\Compatibility\sitepress_multilingual_cms\WpmlElementIdItemValueHandler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\Compatibility\sitepress_multilingual_cms\WpmlTridItemValueHandler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\Compatibility\types\ToolsetGroupIdItemValueHandler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\JsonItemValueHandler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\OptionItemValueHandler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\PostGuidItemValueHandler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ReferenceFieldItemValueHandler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ReferenceItemValueHandler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ScalarItemValueHandler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ValueProcessor\AttachmentProcessor;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ValueProcessor\ClearDataOnImportProcessor;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ValueProcessor\ClearElementorCacheOnImportProcessor;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ValueProcessor\ImportAuthorProcessor;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ValueProcessor\TransmutationHandler\ClearDataTransmutationHandler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ValueProcessor\TransmutationHandler\DelimitedTransmutationHandler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ValueProcessor\TransmutationHandler\ElementorProConditionTransmutationHandler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ValueProcessor\TransmutationHandler\FileTransmutationHandler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ValueProcessor\TransmutationHandler\HostNameTransmutationHandler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ValueProcessor\TransmutationHandler\ReferenceTransmutationHandler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ValueProcessor\TransmutationHandler\ShortcodeReferencesTransmutationHandler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ValueProcessor\TransmutationProcessor;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ValueProcessor\UrlHostNameProcessor;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ScreenLabelFormatter\FieldScreenLabelFormatter;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ScreenLabelFormatter\ReferencedItemLabelScreenLabelFormatter;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ScreenLabelFormatter\ScreenLabelFormatterBase;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\TrackableItem;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\TrackerConfig\TrackerConfigProviderManager;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\TrackingManager;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\TrackScheduler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\Api;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\DeployerConfigRepository\EalabConfigRepository;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\FileManager;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\Settings;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\Translator;

// Todo: document why this is needed. (The plugin works without this.)
ob_start();

define( 'DEPLOY_SYNC_CONTENT__VERSION', '2.2.0' );
define( 'DEPLOY_SYNC_CONTENT__PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'DEPLOY_SYNC_CONTENT__PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'DEPLOY_SYNC_CONTENT__PLUGIN_FILE', __FILE__ );
define( 'DEPLOY_SYNC_CONTENT__SLUG', 'deploy-sync-content' );
define( 'DEPLOY_SYNC_CONTENT__BASIC_URI', 'admin.php?page=deploy-sync-content' );
define( 'DEPLOY_SYNC_CONTENT__LICENCE_URL', 'https://wp-deployer.io' );

// Use this to switch to the v2 forms.
// Todo: v1 form is deprecated and utterly outdated.
define( 'DEPLOY_SYNC_CONTENT__TRANSIENT_BUNDLE_FORM_V2', 'deploy_sync_content__bundle_form_v2' );

// Custom autoloader for this plugin.
require_once __DIR__ . '/autoload.php';

// Load the translation utility functions.
Translator::prepare();

require_once DEPLOY_SYNC_CONTENT__PLUGIN_DIR . '/includes/cli.php';
require_once DEPLOY_SYNC_CONTENT__PLUGIN_DIR . '/includes/configuration.php';
require_once DEPLOY_SYNC_CONTENT__PLUGIN_DIR . '/includes/helpers.php';
require_once DEPLOY_SYNC_CONTENT__PLUGIN_DIR . '/includes/kses.php';
require_once DEPLOY_SYNC_CONTENT__PLUGIN_DIR . '/includes/rest-api.php';
require_once DEPLOY_SYNC_CONTENT__PLUGIN_DIR . '/includes/route.php';

/**
 * The Action Scheduler library. Required here as recommended here: https://actionscheduler.org/usage/.
 *
 * This is used for the queue system.
 */
require_once DEPLOY_SYNC_CONTENT__PLUGIN_DIR . 'vendor/woocommerce/action-scheduler/action-scheduler.php';

/**
 * This is the callback for the action scheduler action that processes the item track batches.
 */
TrackScheduler::registerTrackItemActionCallback();

/**
 * Register the standard batch handler callback.
 */
BatchScheduler::registerBatchActionCallback();

/**
 * The core plugin class that is used to define internationalization,
 * admin-specific hooks, and public-facing site hooks.
 */
require DEPLOY_SYNC_CONTENT__PLUGIN_DIR . 'includes/class-deploy-sync-content.php';

/**
 * Begins execution of the plugin
 *
 * @since    1.0.0
 */
function deploy_sync_content__run_dsc() {
	$dsc_plugin = new Deploy_Sync_Content__Main();
	$dsc_plugin->run();
}

deploy_sync_content__run_dsc();

/**
 * Action scheduler callbacks for the import UI.
 */
\EaLab\WP_Deployer\includes\controller\Import_Form_Handler::registerActionSchedulerCallbacks();

/**
 * The base dir of the plugin.
 *
 * @return string The base dir of the plugin.
 */
function deploy_sync_content__get_base_dir( bool $with_trailing_slash = true ): string {
	$upload_dir = wp_upload_dir();
	$plugin_dir = $upload_dir['basedir'] . '/deploy-sync-content';

	// Ensure the plugin directory exists in uploads.
	if ( ! file_exists( $plugin_dir ) ) {
		wp_mkdir_p( $plugin_dir );

		// Create .htaccess file to prevent direct access.
		$htaccess_content = "Order deny,allow\nDeny from all";

		// Initialize WP_Filesystem.
		global $wp_filesystem;
		if ( ! isset( $wp_filesystem ) ) {
			require_once( ABSPATH . 'wp-admin/includes/file.php' );
			WP_Filesystem();
		}

		// Write .htaccess file using WP_Filesystem
		$wp_filesystem->put_contents( $plugin_dir . '/.htaccess', $htaccess_content, FS_CHMOD_FILE );
	}

	return $with_trailing_slash ? trailingslashit( $plugin_dir ) : untrailingslashit( $plugin_dir );
}

add_filter(
	'pre_update_option_' . Settings::OPTIONS__DEPLOY_SYNC_CONTENT__SETTINGS_DATA,
	function ( $value, $old_value ) {
		if ( ! is_array( $value ) ) {
			// This should never happen.
			return $value;
		}

		// Normalize the url_prod input.
		$value['url_prod'] = rtrim( $value['url_prod'], '/' );

		foreach ( $old_value as $sub_key => $sub_value ) {
			if ( array_key_exists( $sub_key, $value ) ) {
				continue;
			}

			// Preserve keys. Indeed, some keys may not be edited directly on the main option form.
			$value[$sub_key] = $sub_value;
		}

		// Encode the required fields to base 64.
		// This is at least needed for the url_prod to prevent it
		// from being transmuted when cloning site databases
		// with automatic domain replacement.
		return Settings::encodeSettingsToBase64( $value );
	},
	10,
	2
);

add_action(
	'updated_option',
	function ( string $option_name, $old_value, $value ) {
		if ( Settings::OPTIONS__DEPLOY_SYNC_CONTENT__SETTINGS_DATA === $option_name ) {
			Settings::getInstance()->onSettingsUpdate( $value );

			if ( get_option( 'deploy_sync_content__tracking_init_once' ) ) {
				// The tracking has already been initialized once. We can reset the tracking if needed.
				$prod_url = $value['url_prod'] ?? '';

				if ( ! empty( $prod_url ) && ( $prod_url !== $old_value['url_prod'] ?? 'no-crash' ) ) {
					// By checking if $prod_url is empty, we prevent accidental retracking, especially
					// when the settings validation failed to validate the URL.
					// We have to retrack all the items.
					$tracking_manager = ComponentBasedObject::getRootComponentByClassName( TrackingManager::class, true );
					$tracking_manager->resetTracking( array( 'retrack_items' => true ) );
				}
			}
		}
	},
	10,
	3
);

/**
 * Add Custom css and JS
 * Quang-Minh Test
 */
add_action(
	'admin_enqueue_scripts',
	function () {
		wp_enqueue_style(
			'deploy-sync-content-diff-style',
			plugins_url( '/assets/css/diff.css', __FILE__ ),
			array(),
			'1.0.0'
		);
	}
);

add_filter(
	'deploy_sync_content__get_config_provider_managers',
	static function ( array $data = array() ): array {
		$data[ ExclusionsConfigProviderManager::getManagerId() ]                              = ExclusionsConfigProviderManager::class;
		$data[ TrackerConfigProviderManager::getManagerId() ]                                 = TrackerConfigProviderManager::class;
		$data[ TransmutationHandlerShortcodeReferencesConfigProviderManager::getManagerId() ] = TransmutationHandlerShortcodeReferencesConfigProviderManager::class;
		$data[ ValueHandlerOptionConfigProviderManager::getManagerId() ]                      = ValueHandlerOptionConfigProviderManager::class;

		return $data;
	},
	10,
	1
);

function deploy_sync_content__get_config_repository(): ?string {
	$configuration_method = Settings::getInstance()->get( 'configuration_method' );

	if ( 'auto' === $configuration_method ) {
		return EalabConfigRepository::class;
	}

	if ( 'manual' === $configuration_method ) {
		return BasicConfigRepository::class;
	}

	return null;
}

add_filter(
	'deploy_sync_content__get_config_repository',
	'deploy_sync_content__get_config_repository',
	10,
	1
);

TrackDateInitializerManager::onPluginInit();

add_action(
	'deploy_sync_content__licence_manager__on_licence_key_successful_revalidation',
	/**
	 * Launched on licence validated.
	 *
	 * @throws InvalidConfigException Configuration error.
	 * @throws WpDeployException Configuration error.
	 * @throws UnmetDependencyException Error in instantiation.
	 */
	static function (): void {
		// Initialize the configuration if needed.
		// This will build and save the config if it does not exist yet.
		// Licence error will not fire in this function because everything was checked beforehand.
		$config_provider_managers = apply_filters( 'deploy_sync_content__get_config_provider_managers', array() );

		foreach ( $config_provider_managers as $config_provider_manager ) {
			/** @var ConfigProviderManagerBase $config_provider_instance */
			$config_provider_instance = ComponentBasedObject::getRootComponentByClassName( $config_provider_manager, true );

			// If already initialized, this will do nothing.
			$config_provider_instance->getActiveConfig();
		}
	},
	10,
	0
);

add_action(
	'deploy_sync_content__refresh_config',
	/**
	 * Refreshes the config.
	 */
	static function (): void {
		$config_provider_managers = apply_filters( 'deploy_sync_content__get_config_provider_managers', array() );

		foreach ( $config_provider_managers as $config_provider_manager ) {
			/** @var ConfigProviderManagerBase $config_provider_manager_instance */
			$config_provider_manager_instance = ComponentBasedObject::getRootComponentByClassName( $config_provider_manager, true );

			// Discard any local changes and use the configuration from the repository and other sources.
			$config_provider_manager_instance->updateCurrentConfig( true, array(), array( 'keep_existing' => false ) );
		}
	},
	10,
	0
);

add_action(
	'deploy_sync_content__reindex_items',
	/**
	 * Reindexes the items.
	 */
	static function (): void {
		$tracking_manager = ComponentBasedObject::getRootComponentByClassName( TrackingManager::class, true );

		$tracking_manager->retrackItems();

		update_option( 'deploy_sync_content__tracking_init_once', 0 );
	},
	10,
	0
);

/**
 * Demonstrates how to alter the config providers definitions.
 *
 * Config providers are used to retrieve the configuration from a source.
 *
 * See ConfigProviderManagerBase::buildConfigProviders() for more details.
 */
add_filter(
	'deploy_sync_content__config_manager__tracker__config_providers_defs__alter',
	function ( array $config_providers ): array {
		/**
		 * Example of how to add a config provider.
		 *
		 * @code
		 * $config_providers[] = array(
		 *     'class' => MyConfigProvider::class,
		 *     'options' => array( 'some_option' => true ),
		 * );
		 * @endcode
		 */
		return $config_providers;
	}
);

/*
 * Deploy & Sync Content core item value handlers.
 */
add_filter(
	'deploy_sync_content__item_value_handler_manager__get_class_names',
	function ( array $value_handlers ) {
		$handlers = array(
			JsonItemValueHandler::class,
			OptionItemValueHandler::class,
			PostGuidItemValueHandler::class,
			ReferenceItemValueHandler::class,
			ReferenceFieldItemValueHandler::class,
			ScalarItemValueHandler::class,
		);

		foreach ( $handlers as $handler_class_name ) {
			$value_handlers[ $handler_class_name::getHandlerBaseId() ] = $handler_class_name;
		}

		return $value_handlers;
	}
);

/*
 * Third party item value handlers.
 */
add_filter(
	'deploy_sync_content__item_value_handler_manager__get_class_names',
	function ( array $value_handlers ) {
		$types_toolset_handlers = array(
			ToolsetGroupIdItemValueHandler::class,
		);

		$wpml_handlers = array(
			WpmlElementIdItemValueHandler::class,
			WpmlTridItemValueHandler::class,
		);

		$handlers = array_merge( $wpml_handlers, $types_toolset_handlers );

		foreach ( $handlers as $handler_class_name ) {
			$value_handlers[ $handler_class_name::getHandlerBaseId() ] = $handler_class_name;
		}

		return $value_handlers;
	}
);

add_filter(
	'deploy_sync_content__screen_label_formatter_manager__get_class_names',
	function ( array $class_names ) {
		$class_names[ FieldScreenLabelFormatter::getId() ]               = FieldScreenLabelFormatter::class;
		$class_names[ ReferencedItemLabelScreenLabelFormatter::getId() ] = ReferencedItemLabelScreenLabelFormatter::class;
		$class_names[ ScreenLabelFormatterBase::getId() ]                = ScreenLabelFormatterBase::class;

		return $class_names;
	}
);

/**
 * This filter is used for telling which transmutation handlers are available.
 */
add_filter(
	'deploy_sync_content__transmutation_handler_manager__get_class_names',
	function ( array $class_names ) {
		$class_names[ ClearDataTransmutationHandler::getId() ]             = ClearDataTransmutationHandler::class;
		$class_names[ DelimitedTransmutationHandler::getId() ]             = DelimitedTransmutationHandler::class;
		$class_names[ ElementorProConditionTransmutationHandler::getId() ] = ElementorProConditionTransmutationHandler::class;
		$class_names[ FileTransmutationHandler::getId() ]                  = FileTransmutationHandler::class;
		$class_names[ HostNameTransmutationHandler::getId() ]              = HostNameTransmutationHandler::class;
		$class_names[ ReferenceTransmutationHandler::getId() ]             = ReferenceTransmutationHandler::class;
		$class_names[ ShortcodeReferencesTransmutationHandler::getId() ]   = ShortcodeReferencesTransmutationHandler::class;

		return $class_names;
	}
);

/**
 * This hook is invoked when the value handler "option" is used, for each item tracker.
 *
 * It allows to alter the config of the option value handler right before it is used.
 * This altered value is not saved in any storage. It is used in real-time for the current process only.
 * See OptionItemValueHandler::configure() for more details on the structure and usage.
 */
add_filter(
	'deploy_sync_content__config_collector__value_handler__option',
	function ( array $value ) {
		/**
		 * Example on how to exclude an option from being exported.
		 *
		 * @code
		 * $value['options']['options.option_value']['fields']['home'] = array( 'exclude' => true );
		 * @endcode
		 */
		return $value;
	}
);

add_filter(
	'deploy_sync_content__value_processor_manager__get_class_names',
	function ( array $processor_class_names ) {
		$processor_class_names[ AttachmentProcessor::getId() ]                  = AttachmentProcessor::class;
		$processor_class_names[ ClearDataOnImportProcessor::getId() ]           = ClearDataOnImportProcessor::class;
		$processor_class_names[ ClearElementorCacheOnImportProcessor::getId() ] = ClearElementorCacheOnImportProcessor::class;
		$processor_class_names[ ImportAuthorProcessor::getId() ]                = ImportAuthorProcessor::class;
		$processor_class_names[ TransmutationProcessor::getId() ]               = TransmutationProcessor::class;
		$processor_class_names[ UrlHostNameProcessor::getId() ]                 = UrlHostNameProcessor::class;

		return $processor_class_names;
	}
);

// Make sure the track queue table exists before wrapping wpdb.
// This is needed at installation time, or else the wrapper will attempt
// to populate the track queue table, which will fail because it does not exist.
TrackingManager::createTrackQueueTable();

/* ************* SQL Query Analysis ****************** */
if ( get_option( 'deploy_sync_content__tracking_init_once' ) ) {
	// Start wrapping wpdb only when the tracking is ready,
	// after the licence has been validated at least once.
	require_once __DIR__ . '/vendor/_prefixed/ealab/deployer-core/src/Utility/WpdbWrapper.php';
}

register_activation_hook(
	__FILE__,
	function () {
		ComponentBasedObject::getRootComponentByClassName( DeploymentManager::class, true )->onPluginActivation();
	}
);

register_deactivation_hook(
	__FILE__,
	function () {
		ComponentBasedObject::getRootComponentByClassName( DeploymentManager::class, true )->onPluginDeactivation();
	}
);

/**
 * Transient notices.
 */
add_action(
	'admin_notices',
	static function () {
		$transients_to_check = array(
			'deploy_sync_content__activation_message',
		);

		foreach ( $transients_to_check as $transient_to_check ) {
			$notice = get_transient( $transient_to_check );
			if ( $notice ) {
				echo wp_kses_post( $notice );

				delete_transient( $transient_to_check );
			}
		}
	},
	10,
	0
);

function deploy_sync_content__settings_init() {
	add_settings_section(
		'deploy-sync-content--settings-section-licence',
		esc_html__( 'Deploy & Sync Content Licence', 'deploy-sync-content' ),
		'',
		'deploy-sync-content--settings-page'
	);

	add_settings_section(
		'deploy-sync-content--settings-section-basic-auth',
		esc_html__( 'Basic Auth', 'deploy-sync-content' ) . '&nbsp;&nbsp;<a href="javascript:;" class="tooltip-icon" data-bs-placement="top" data-bs-custom-class="custom-tooltip" data-bs-toggle="tooltip" data-bs-title="' . esc_html__( 'For server protection purposes.', 'deploy-sync-content' ) . '"><i class="bi bi-question-circle-fill"></i></a>',
		'',
		'deploy-sync-content--settings-page'
	);

	add_settings_section(
		'deploy-sync-content--settings-section',
		esc_html__( 'General', 'deploy-sync-content' ),
		'',
		'deploy-sync-content--settings-page'
	);

	add_settings_field(
		'deploy-sync-content--licencekey',
		esc_html__( 'Licence Key', 'deploy-sync-content' ),
		'deploy_sync_content__settings_licencekey_cb',
		'deploy-sync-content--settings-page',
		'deploy-sync-content--settings-section-licence'
	);

	register_setting(
		'deploy-sync-content--settings-page',
		'deploy_sync_content__settings_data',
		array(
			'sanitize_callback' => static function ( $data ) {
				// Old values.
				$old_settings = Settings::getInstance()->getAll();

				if ( empty( $data['url_prod'] ) ) {
					// This is not allowed; this value MUST be set.
					add_settings_error( 'deploy_sync_content__settings_data', 'url_prod', esc_html__( 'The production URL value must be set.', 'deploy-sync-content' ) );

					$data['url_prod'] = $old_settings['url_prod'] ?? '';
				} elseif ( Settings::sanitizeProdUrl( get_option( 'siteurl' ) ) !== Settings::sanitizeProdUrl( $data['url_prod'] ) ) {
					try {
						// The production URL does not lead to the current instance.
						// Ping the production server with the new URL.
						$pong = Api::callApiProdV2(
							'GET',
							'/wp-json/deploy-sync-content/v1/prod/ping',
							array(
								'prod_url_override' => Settings::sanitizeProdUrl( $data['url_prod'] ),
							)
						);

						if ( ! $pong ) {
							throw new WpDeployException( esc_html__( 'The production server did not return a valid response.', 'deploy-sync-content' ) );
						}
					} catch ( \Throwable $throwable ) {
						$message            = esc_html__( 'Could not save the production URL setting.', 'deploy-sync-content' );
						$additional_message = $throwable->getMessage();

						if ( $additional_message ) {
							// Translators: Additional info about an error.
							$message .= ' ' . sprintf( esc_html__( 'Reason: %s', 'deploy-sync-content' ), $additional_message );
						}

						add_settings_error( 'deploy_sync_content__settings_data', 'url_prod', $message );

						$data['url_prod'] = $old_settings['url_prod'] ?? '';
					}
				}

				return $data;
			},
		)
	);

	add_settings_field(
		'deploy-sync-content--urlprod',
		esc_html__( 'URL Production', 'deploy-sync-content' ),
		'deploy_sync_content__settings_urlprod_cb',
		'deploy-sync-content--settings-page',
		'deploy-sync-content--settings-section'
	);

	add_settings_field(
		'deploy-sync-content--syncprod',
		esc_html__( 'Enable live site comparison', 'deploy-sync-content' ),
		'deploy_sync_content__settings_syncprod_cb',
		'deploy-sync-content--settings-page',
		'deploy-sync-content--settings-section'
	);

	add_settings_field(
		'deploy-sync-content--allow-distant-retrieval',
		esc_html__( 'Allow data retrieval from distant instance', 'deploy-sync-content' ),
		'deploy_sync_content__settings_allow_distant_retrieval_cb',
		'deploy-sync-content--settings-page',
		'deploy-sync-content--settings-section'
	);

	add_settings_field(
		'deploy-sync-content--accesstoken',
		esc_html__( 'Access Token', 'deploy-sync-content' ),
		'deploy_sync_content__settings_accesstoken_cb',
		'deploy-sync-content--settings-page',
		'deploy-sync-content--settings-section'
	);

	// Basic auth section.

	// Checkbox for enabling basic auth.
	add_settings_field(
		'deploy-sync-content--basic-auth-enable',
		esc_html__( 'Enable Basic Auth', 'deploy-sync-content' ),
		'deploy_sync_content__settings_basic_auth_enable_cb',
		'deploy-sync-content--settings-page',
		'deploy-sync-content--settings-section-basic-auth'
	);

	// Username and password fields for basic auth.
	add_settings_field(
		'deploy-sync-content--basic-auth-username',
		esc_html__( 'Basic Auth Username', 'deploy-sync-content' ),
		'deploy_sync_content__settings_basic_auth_username_cb',
		'deploy-sync-content--settings-page',
		'deploy-sync-content--settings-section-basic-auth'
	);

	add_settings_field(
		'deploy-sync-content--basic-auth-password',
		esc_html__( 'Basic Auth Password', 'deploy-sync-content' ),
		'deploy_sync_content__settings_basic_auth_password_cb',
		'deploy-sync-content--settings-page',
		'deploy-sync-content--settings-section-basic-auth'
	);
	// End basic auth section.

	add_settings_field(
		'deploy-sync-content--importauthor',
		esc_html__( 'Default Import Author', 'deploy-sync-content' ) . '&nbsp;&nbsp;<a href="javascript:;" class="tooltip-icon" data-bs-placement="top" data-bs-custom-class="custom-tooltip" data-bs-toggle="tooltip" data-bs-title="' . __( 'The user used when importing new posts', 'deploy-sync-content' ) . '"><i class="bi bi-question-circle-fill"></i></a>',
		'deploy_sync_content__settings_importauthor_cb',
		'deploy-sync-content--settings-page',
		'deploy-sync-content--settings-section'
	);
}

add_action( 'admin_init', 'deploy_sync_content__settings_init' );

function deploy_sync_content__settings_urlprod_cb() {
	$option_value = Settings::getInstance()->get( 'url_prod', '' );
	echo "<input id='deploy_sync_content__setting_url_prod' name='deploy_sync_content__settings_data[url_prod]' type='url' value='" . esc_attr( $option_value ) . "' />";
	?>
	<div><?php echo esc_html__( 'The production instance location. Start with the scheme (i.e. https://).', 'deploy-sync-content' ); ?></div>
	<div>
		<strong><?php echo esc_html__( 'Warning!', 'deploy-sync-content' ); ?></strong> <?php echo esc_html__( 'Changing this value will reset any tracking previously performed locally. In other words, a full re-indexation will occur to match the local items with those on the new production instance.', 'deploy-sync-content' ); ?>
	</div>
	<?php
}

function deploy_sync_content__settings_syncprod_cb() {
	$option_value = Settings::getInstance()->get( 'sync_prod', '' );
	echo "<input id='deploy_sync_content__setting_sync_prod' name='deploy_sync_content__settings_data[sync_prod]' type='checkbox' value='1' " . checked( 1, esc_attr( $option_value ), false ) . " />";
}

function deploy_sync_content__settings_allow_distant_retrieval_cb() {
	$option_value = Settings::getInstance()->get( 'allow_distant_retrieval', '0' );
	echo "<input id='deploy_sync_content__setting_allow_distant_retrieval' name='deploy_sync_content__settings_data[allow_distant_retrieval]' type='checkbox' value='1' " . checked( 1, esc_attr( $option_value ), false ) . " />";
}

function deploy_sync_content__settings_accesstoken_cb() {
	$option_value = Settings::getInstance()->get( 'access_token', '' );
	echo "<input id='deploy_sync_content__setting_access_token' name='deploy_sync_content__settings_data[access_token]' type='password' value='" . esc_attr( $option_value ) . "' />";
}

function deploy_sync_content__settings_basic_auth_enable_cb() {
	$option_value = Settings::getInstance()->get( 'basic_auth_enable', '' );
	echo "<input id='deploy_sync_content__setting_basic_auth_enable' name='deploy_sync_content__settings_data[basic_auth_enable]' type='checkbox' value='1' " . checked( 1, esc_attr( $option_value ), false ) . ' />';
}

function deploy_sync_content__settings_basic_auth_username_cb() {
	$option_value = Settings::getInstance()->get( 'basic_auth_username', '' );
	echo "<input id='deploy_sync_content__setting_basic_auth_username' name='deploy_sync_content__settings_data[basic_auth_username]' type='text' value='" . esc_attr( $option_value ) . "' />";
}

function deploy_sync_content__settings_basic_auth_password_cb() {
	$option_value = Settings::getInstance()->get( 'basic_auth_password', '' );
	echo "<input id='deploy_sync_content__setting_basic_auth_password' name='deploy_sync_content__settings_data[basic_auth_password]' type='password' value='" . esc_attr( $option_value ) . "' />";
}

function deploy_sync_content__settings_licencekey_cb() {
	$option_value = Settings::getInstance()->get( 'licence_key', '' );
	?>
	<input
		id="deploy_sync_content__setting_licence_key"
		name="deploy_sync_content__settings_data[licence_key]"
		type="password"
		value="<?php echo esc_attr( $option_value ); ?>"/>
	<div>If EA Lab provided you a key, you can set it here. <strong>Leave empty to use the default key.</strong></div>
	<div><strong>Note:</strong> This key does not unlock any functionality of the <em>Deploy & Sync Content</em> base
		plugin.
	</div>
	<div>It is used for debugging when you interact with the support team.</div>
	<?php
}

function deploy_sync_content__settings_importauthor_cb() {
	$args_author   = array( 'orderby' => 'display_name' );
	$wp_user_query = new WP_User_Query( $args_author );
	$authors       = $wp_user_query->get_results();

	$setting_data_name = ImportAuthorProcessor::OPTIONS__IMPORT_AUTHOR;

	$selected_option_value = (string) Settings::getInstance()->get( $setting_data_name, '' );

	if ( ! in_array( $selected_option_value, array_map( static fn( $a ) => (string) $a->ID, $authors ), true ) ) {
		// The user ID currently set is not valid for this instance. Use the current user instead.
		$user = wp_get_current_user();

		if ( $user ) {
			$selected_option_value = (string) $user->ID ?? '';
		}
	}

	if ( ! empty( $authors ) ) {
		echo "<select id='deploy_sync_content__setting_import_author' name='deploy_sync_content__settings_data[" . esc_html( $setting_data_name ) . "]'>";
		foreach ( $authors as $author ) {
			$author_info = get_userdata( $author->ID );
			echo '<option value="' . esc_attr( $author->ID ) . '" ';
			echo ( $selected_option_value === $author->ID ) ? ' selected' : '';
			echo '>' . esc_html( $author_info->nickname ) . '</option>';
		}
		echo "</select>";
	}
}


/**
 * Appends a JS script that alerts the user when the current post is changed on production.
 */
function deploy_sync_content__enqueue_merge_alert_script() {
	$tracking_manager = ComponentBasedObject::getRootComponentByClassName( TrackingManager::class, true );

	if ( $tracking_manager->isProductionEnvironment() ) {
		return;
	}

	if ( 'GET' !== strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) ) {
		// Only display the alert when loading the editor with the GET method.
		return;
	}

	// @TODO: make this extendable for third-party editors.
	if ( ( ! empty( $_GET['post'] ) && in_array( get_post_type( sanitize_text_field( wp_unslash( $_GET['post'] ) ) ), array(
				'page',
				'post',
			) ) )
		// Elementor.
		|| ( ( sanitize_text_field( wp_unslash( $_GET['action'] ?? '' ) ) ) === 'elementor' )
		// WP Bakery Page Builder.
		|| ( ( sanitize_text_field( wp_unslash( $_GET['vc_action'] ?? '' ) ) ) === 'vc_inline' && in_array( get_post_type( sanitize_text_field( wp_unslash( $_GET['post_id'] ?? '' ) ) ), array(
				'page',
				'post',
			) ) )
	) {
		// We are in an edit context.
		$post_id = sanitize_text_field( wp_unslash( $_GET['post'] ?? $_GET['post_id'] ?? 0 ) );

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return;
		}

		$item_tracker = $tracking_manager->getItemTrackerByTrackerId( 'posts' );

		if ( empty( $item_tracker ) ) {
			// No posts tracker.
			return;
		}

		$uuid_post = $item_tracker->getItemUuidByLocalId( $post_id );

		if ( empty( $uuid_post ) ) {
			// The post is not (yet) tracked. No need to check for changes.
			return;
		}

		$item_tracker_data = $item_tracker->loadTrackableItem( $uuid_post );

		if ( ! $item_tracker_data instanceof TrackableItem ) {
			// This should never happen under normal circumstances.
			// It means a post has an UUID but is not tracked,
			// which is suspicious.
			return;
		}

		$exported_item = $item_tracker_data->buildExportData();

		$tmd_table_name = $item_tracker->getTrackingMetaDataTableName( false );

		$local_last_modification_date = $exported_item['fields'][ $tmd_table_name . '.last_modification_date' ];

		$incoming_data = deploy_sync_content__check_data_changed_prod( $uuid_post, array( $exported_item ) );

		if ( empty( $incoming_data ) ) {
			// The production instance does not have this content.
			return;
		}

		$new_data_input = $incoming_data['items_to_import'];

		$prod_item = $new_data_input[ $item_tracker_data->getFullItemId() ];

		$prod_last_modification_date = ! empty( $new_data_input )
			? $prod_item['fields'][ $tmd_table_name . '.last_modification_date' ]
			: '';

		if ( empty( $prod_last_modification_date ) ) {
			// Invalid or missing last_modification_date value coming from prod.
			return;
		}

		$importability = $incoming_data['importability'];

		if ( $importability & Importability::BASED_ON_SAME_PRODUCTION_VALUES
			|| $local_last_modification_date === $prod_last_modification_date ) {
			// Based on same production values,
			// or the local item IS the item which has been imported on
			// prod (the local LMD matches the prod dates).
			return;
		}

		// Register the alert script.
		wp_enqueue_script( 'deploy-sync-content--custom-alert-script', plugin_dir_url( __FILE__ ) . 'admin/assets/js/custom-alert.js', array( 'jquery' ), '1.0', true );

		// Include the result directly in the JS space.
		// The new data is base64 encoded to prevent escaping issues
		// due to WP slashing the post data.
		wp_add_inline_script(
			'deploy-sync-content--custom-alert-script',
			'var deploy_sync_content__ajax_url = \'' . esc_url( admin_url( 'admin.php?page=deploy-sync-content&tab=importpost' ) ) . '\'; var deploy_sync_content__import_data = \'' . esc_js( base64_encode( json_encode( $new_data_input ) ) ) . '\'; var deploy_sync_content__nonce = \'' . esc_js( wp_create_nonce( DEPLOY_SYNC_CONTENT__SLUG . 'importpost' ) ) . '\';',
			'before'
		);

		if ( isset( $_GET['action'] ) && 'elementor' === sanitize_text_field( wp_unslash( $_GET['action'] ) ) ) {
			wp_enqueue_style( 'deploy-sync-content--alert-style', plugin_dir_url( __FILE__ ) . 'admin/assets/css/plugin-style.css', array(), '1.0', 'all' );
		}
	}
}

add_action( 'admin_enqueue_scripts', 'deploy_sync_content__enqueue_merge_alert_script' );
add_action( 'elementor/editor/before_enqueue_scripts', 'deploy_sync_content__enqueue_merge_alert_script' );

function deploy_sync_content__remove_item() {
	if ( ! check_ajax_referer( DEPLOY_SYNC_CONTENT__SLUG . 'deploy_sync_content_remove_item', '_wpnonce', false ) ) {
		wp_send_json_error( array( 'message' => esc_html__( 'Invalid security token.', 'deploy-sync-content' ) ) );
		die();
	}

	if ( ! current_user_can( 'manage_options' ) ) {
		wp_send_json_error( array( 'message' => esc_html__( 'You do not have sufficient permissions to access this page.', 'deploy-sync-content' ) ) );
		die();
	}

	$bundle_subdirectory = sanitize_text_field( wp_unslash( $_POST['id'] ?? '' ) );

	if ( empty( $bundle_subdirectory ) ) {
		wp_send_json_error( array( 'message' => esc_html__( 'Invalid bundle ID.', 'deploy-sync-content' ) ) );
		die();
	}

	$file_manager = deploy_sync_content__get_root_cbo( FileManager::class );
	$file_manager->removeBundleDirectory( $bundle_subdirectory );
	wp_send_json_success();
	die();
}

add_action( 'wp_ajax_deploy_sync_content__remove_item', 'deploy_sync_content__remove_item' );

$current_page = sanitize_text_field( wp_unslash( $_GET['page'] ?? '' ) );
$current_tab  = sanitize_text_field( wp_unslash( $_GET['tab'] ?? '' ) );

if ( ! ( isset( $_GET['page'] )
	&& 'deploy-sync-content' === $current_page
	&& isset( $_GET['tab'] )
	&& 'tracking-queue-progress' === $current_tab ) ) {
	/**
	 * We are not on the progress page. Display the notice.
	 * Add a notice to the admin area if there are any pending tracking queue steps.
	 */
	add_action(
		'admin_notices',
		function () {
			// This is the batch ID for the batch tracking queue steps.
			$batch_id = 'tracking-queue';

			// Initialize Action Scheduler, as it could be not yet initialized
			// due to plugins which do admin_notices during the WP init event.
			empty( did_action( 'action_scheduler_init' ) ) && do_action( 'action_scheduler_init' );

			// Check if there are any running initialization batches.
			$pending_actions = as_get_scheduled_actions(
				array(
					'hook'   => 'deploy_sync_content/track_item',
					'group'  => $batch_id,
					'status' => 'pending',
				),
			);

			if ( 0 === count( $pending_actions ) ) {
				// No remaining initialization batches.
				return;
			}

			?>
			<div class="notice notice-info is-dismissible">
				<p>
					<?php
					echo wp_kses(
					// translators: %s is a URL.
						sprintf( __( '<em>Deploy & Sync Content</em> is indexing this site content. <a href="%s">Click here</a> to see the progress.', 'deploy-sync-content' ), admin_url( 'admin.php?page=deploy-sync-content&tab=tracking-queue-progress' ) ),
						array( 'em' => array(), 'a' => array( 'href' => array() ) ) );
					?>
				</p>
			</div>
			<?php
		}
	);
}
if ( get_option( 'deploy_sync_content__tracking_init_once' ) ) {
	// The tracking config has been set once.
	// Everything is loaded. Upgrade Deploy & Sync Content data.
	try {
		DeploymentManager::upgradePluginData();
	} catch ( Throwable $e ) {
		// Afficher un message d'erreur WordPress classique
		add_action(
			'admin_notices',
			function () use ( $e ) {
				?>
				<div class="notice notice-error">
					<p>
						<?php
						echo wp_kses(
							sprintf(
								__( '<strong>Deploy & Sync Content Error:</strong> An error occurred while upgrading plugin data: %s', 'deploy-sync-content' ),
								esc_html( $e->getMessage() )
							),
							array( 'strong' => array() )
						);
						?>
					</p>
				</div>
				<?php
			}
		);
	}
}

add_action(
	'init',
	static function (): void {
		if ( get_option( 'deploy_sync_content__tracking_init_once' ) ) {
			return;
		}

		if ( ! get_option( 'deploy_sync_content__url_prod_is_defined' ) ) {
			return;
		}

		// This is the batch ID for the batch tracking queue steps.
		$batch_id = 'tracking-queue';

		// Initialize Action Scheduler, as it could be not yet initialized
		// due to plugins which do admin_notices during the WP init event.
		empty( did_action( 'action_scheduler_init' ) ) && do_action( 'action_scheduler_init' );

		// Check if there are any running initialization batches.
		$pending_actions = as_get_scheduled_actions(
			array(
				'hook'   => 'deploy_sync_content/track_item',
				'group'  => $batch_id,
				'status' => 'pending',
			),
		);

		if ( 0 === count( $pending_actions ) ) {
			// No remaining initialization batches.
			// Mark the tracking init as done.
			get_option( 'deploy_sync_content__tracking_init_once' ) || update_option( 'deploy_sync_content__tracking_init_once', 1 );
		}
	},
	// Must be greater than 0 because Action Scheduler inits at 1 during the init phase.
	5
);

add_filter(
	'deploy_sync_content__export_limit',
	static function ( $limit, $tracker_id ) {
		if ( 'posts' === $tracker_id ) {
			// Dev note: this limit is there to limit issues when too many items have to sync.
			return 300;
		}

		return $limit;
	},
	-10,
	2
);
