<?php
defined( 'ABSPATH' ) or die( 'Something went wrong.' );

/** --------------------------------------------------------------------------------------------- */
/** ADMIN POST / AJAX CALLBACKS ================================================================= */
/** --------------------------------------------------------------------------------------------- */

/**
 * Scan callback.
 */
add_action( 'admin_post_secupress_scanner', 'secupress_scanit_ajax_post_cb' );
add_action( 'wp_ajax_secupress_scanner',    'secupress_scanit_ajax_post_cb' );
/**
 * Used to scan a test in scanner page.
 * Prints a JSON or redirects the user.
 *
 * @since 1.0
 */
function secupress_scanit_ajax_post_cb() {
	if ( empty( $_GET['test'] ) ) {
		secupress_admin_die();
	}

	$test_name        = $_GET['test']; // WPCS: XSS ok.
	$for_current_site = ! empty( $_GET['for-current-site'] );
	$site_id          = $for_current_site && ! empty( $_GET['site'] ) ? '-' . absint( $_GET['site'] ) : '';

	secupress_check_user_capability( $for_current_site );
	secupress_check_admin_referer( 'secupress_scanner_' . $test_name . $site_id );

	if ( isset( $_GET['delay'] ) ) {
		$delay  = max( min( 5, (int) $_GET['delay'] ), 0 );
		sleep( $delay );
	}
	$response   = secupress_scanit( $test_name, wp_doing_ajax(), $for_current_site );
	secupress_admin_send_response_or_redirect( $response );
}


/**
 * Fix callback.
 */
add_action( 'admin_post_secupress_fixit', 'secupress_fixit_ajax_post_cb' );
add_action( 'wp_ajax_secupress_fixit',    'secupress_fixit_ajax_post_cb' );
/**
 * Used to automatically fix a test in scanner page.
 * Prints a JSON or redirects the user.
 *
 * @since 1.0
 */
function secupress_fixit_ajax_post_cb() {
	if ( empty( $_GET['test'] ) ) {
		secupress_admin_die();
	}

	$test_name        = esc_attr( $_GET['test'] );
	$for_current_site = ! empty( $_GET['for-current-site'] );
	$site_id          = $for_current_site && ! empty( $_GET['site'] ) ? '-' . absint( $_GET['site'] ) : '';

	secupress_check_user_capability( $for_current_site );
	secupress_check_admin_referer( 'secupress_fixit_' . $test_name . $site_id );

	$doing_ajax = wp_doing_ajax();
	$response   = secupress_fixit( $test_name, $doing_ajax, $for_current_site );

	// If not ajax, perform a scan.
	if ( ! $doing_ajax ) {
		secupress_scanit( $test_name, false, $for_current_site );
	}

	secupress_admin_send_response_or_redirect( $response );
}


/**
 * Manual fix callback.
 */
add_action( 'admin_post_secupress_manual_fixit', 'secupress_manual_fixit_ajax_post_cb' );
add_action( 'wp_ajax_secupress_manual_fixit',    'secupress_manual_fixit_ajax_post_cb' );
/**
 * Used to manually fix a test in scanner page.
 * Prints a JSON or redirects the user.
 *
 * @since 1.0
 */
function secupress_manual_fixit_ajax_post_cb() {
	if ( empty( $_POST['test'] ) ) { // WPCS: CSRF ok.
		secupress_admin_die();
	}

	$test_name        = esc_attr( $_POST['test'] ); // WPCS: CSRF ok.
	$for_current_site = ! empty( $_POST['for-current-site'] ); // WPCS: CSRF ok.
	$site_id          = $for_current_site && ! empty( $_POST['site'] ) ? '-' . absint( $_POST['site'] ) : ''; // WPCS: CSRF ok.

	secupress_check_user_capability( $for_current_site );
	secupress_check_admin_referer( 'secupress_manual_fixit_' . $test_name . $site_id );

	$doing_ajax = wp_doing_ajax();
	$response   = secupress_manual_fixit( $test_name, $doing_ajax, $for_current_site );

	// If not ajax, perform a scan.
	if ( ! $doing_ajax ) {
		secupress_scanit( $test_name, false, $for_current_site );
	}

	secupress_admin_send_response_or_redirect( $response );
}


/**
 * Get all translated strings for the scans UI.
 */
add_action( 'wp_ajax_secupress-get-scan-counters', 'secupress_get_scan_counters_ajax_cb' );
/**
 * Used to get all the needed translated strings and counters needed after each single scan/one-click scan.
 *
 * @since 1.0
 */
function secupress_get_scan_counters_ajax_cb() {
	secupress_check_user_capability();
	secupress_check_admin_referer( 'secupress-get-scan-counters' );

	$counts = secupress_get_scanner_counts();

	foreach ( array( 'notscannedyet', 'hasaction', 'good', 'warning', 'bad' ) as $status ) {
		$counts[ $status . '-text' ] = sprintf( _n( '%d issue', '%d issues', $counts[ $status ], 'secupress' ), $counts[ $status ] );
	}

	wp_send_json_success( $counts );
}


/**
 * Date of the last One-click scan.
 */
add_action( 'wp_ajax_secupress-update-oneclick-scan-date', 'secupress_update_oneclick_scan_date_ajax_cb' );
/**
 * Used to update the date of the last One-click scan.
 * Prints a JSON containing the HTML of the new line to insert in the page.
 *
 * @since 1.0
 */
function secupress_update_oneclick_scan_date_ajax_cb() {
	secupress_check_user_capability();
	secupress_check_admin_referer( 'secupress-update-oneclick-scan-date' );

	$items  = array_filter( (array) get_site_option( SECUPRESS_SCAN_TIMES ) );
	// Build the new item.
	$counts = secupress_get_scanner_counts();
	$item   = array(
		'percent' => round( $counts['good'] * 100 / $counts['total'] ),
		'grade'   => $counts['grade'],
		'time'    => time(),
	);

	// Get the previous percentage.
	if ( $items ) {
		$last_percent = end( $items );
		$last_percent = $last_percent['percent'];
	} else {
		$last_percent = -1;
	}

	// Add the new item and limit to 5 results.
	array_push( $items, $item );
	$items = array_slice( $items, -5, 5 );
	update_site_option( SECUPRESS_SCAN_TIMES, $items );

	// Send the formated new item.
	wp_send_json_success( secupress_formate_latest_scans_list_item( $item, $last_percent ) );
}

// WHITELIST IPs
add_action( 'admin_post_secupress-whitelist-ip', 'secupress_whitelist_ip_ajax_post_cb' );
add_action( 'wp_ajax_secupress-whitelist-ip',    'secupress_whitelist_ip_ajax_post_cb' );
/**
 * Whitelist an IP address.
 *
 * @since 1.4.9
 */
function secupress_whitelist_ip_ajax_post_cb() {
	// Make all security tests.
	secupress_check_admin_referer( 'secupress-whitelist-ip' );
	secupress_check_user_capability();

	if ( empty( $_REQUEST['ip'] ) ) {
		secupress_admin_send_message_die( array(
			'message' => __( 'IP address not provided.', 'secupress' ),
			'code'    => 'no_ip',
			'type'    => 'error',
		) );
	}

	// Test the IP.
	$ip          = trim( urldecode( $_REQUEST['ip'] ) );
	$original_ip = $ip;
	$is_list     = false;
	$sep         = "\n";
	if ( strpos( $ip, ', ' ) > 0 ) {
		$sep = ', ';
	} elseif ( strpos( $ip, ',' ) > 0 ) {
		$sep = ',';
	} elseif ( strpos( $ip, ';' ) > 0 ) {
		$sep = ';';
	} elseif ( strpos( $ip, ' ' ) > 0 ) {
		$sep = ' ';
	}
	if ( strpos( $ip, $sep ) > 0 ) {
		$is_list = true;
		$ip      = explode( $sep, $ip );
		$count_1 = count( $ip );
		$ip      = array_filter( $ip , function( $_ip ) {
			return secupress_ip_is_valid( $_ip, true );
		} );
		$count_2 = count( $ip );
	}

	if ( ! $is_list && ! secupress_ip_is_valid( $ip, true ) ) {
		secupress_admin_send_message_die( array(
			'message' => __( 'This is not a valid IP address.', 'secupress' ),
			'code'    => 'invalid_ip',
			'type'    => 'error',
		) );
	}

	if ( ! $is_list && ( secupress_ip_is_whitelisted( $ip ) ) ) {
		secupress_admin_send_message_die( [
			'message' => __( 'This IP address is already allowed.', 'secupress' ),
			'code'    => 'already_whitelisted',
			'type'    => 'error',
		] );
	}

	if ( $is_list && 0 === $count_2 ) {
		secupress_admin_send_message_die( [
			'message' => __( 'The list does not contains any valid IP address.', 'secupress' ),
			'code'    => 'invalid_ip',
			'type'    => 'error',
		] );
	}

	// Add the IP to the option.
	$white_ips = get_site_option( SECUPRESS_WHITE_IP );
	$white_ips = is_array( $white_ips ) ? $white_ips : [];
	if ( ! is_array( $ip ) ) {
		$ip    = [ $ip ];
	}
	$ip        = array_flip( $ip );
	$white_ips = array_merge( $white_ips, $ip );

	update_site_option( SECUPRESS_WHITE_IP, $white_ips );

	/* This hook is documented in /inc/functions/admin.php */
	do_action( 'secupress.ip_allowed', $ip, $white_ips );

	$referer_arg  = '&_wp_http_referer=' . urlencode( esc_url_raw( secupress_admin_url( 'modules', 'logs' ) ) );
	// Send a response.
	if ( ! $is_list ) {
		secupress_admin_send_message_die( [
			'message'    => sprintf( __( 'The IP address %s has been allowed.', 'secupress' ), '<code>' . esc_html( $original_ip ) . '</code>' ),
			'code'       => 'ip_whitelist',
			'tmplValues' => [
				[
					'ip'              => $original_ip,
					'unwhitelist_url' => esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=secupress-unwhitelist-ip&ip=' . esc_attr( $original_ip ) . $referer_arg ), 'secupress-unwhitelist-ip_' . $original_ip ) ),
				],
			] ]
		);
	} else {
		$tmplValues = [];
		foreach ( $white_ips as $_ip => $time ) {
			$tmplValues[] = [   'ip'              => $_ip,
								'unwhitelist_url' => esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=secupress-unwhitelist-ip&ip=' . esc_attr( $_ip ) . $referer_arg ), 'secupress-unwhitelist-ip_' . $_ip ) ),
							];
		}
		if ( $count_1 === $count_2 ) {
			secupress_admin_send_message_die( [
				'message'    => __( 'The IP address list has been allowed.', 'secupress' ),
				'code'       => 'ip_whitelist',
				'tmplValues' => $tmplValues,
			] );
		} else {
			secupress_admin_send_message_die( [
				'message'    => __( 'Some of the IP address list has been allowed.', 'secupress' ),
				'code'       => 'ip_whitelist',
				'tmplValues' => $tmplValues,
			] );
		}
	}
}


add_action( 'admin_post_secupress-unwhitelist-ip', 'secupress_unwhitelist_ip_ajax_post_cb' );
add_action( 'wp_ajax_secupress-unwhitelist-ip',    'secupress_unwhitelist_ip_ajax_post_cb' );
/**
 * Unwhitelist an IP address.
 *
 * @since 1.4.9
 */
function secupress_unwhitelist_ip_ajax_post_cb() {
	// Make all security tests.
	if ( empty( $_REQUEST['ip'] ) ) {
		secupress_admin_send_message_die( array(
			'message' => __( 'IP address not provided.', 'secupress' ),
			'code'    => 'no_ip',
			'type'    => 'error',
		) );
	}

	secupress_check_admin_referer( 'secupress-unwhitelist-ip_' . $_REQUEST['ip'] );
	secupress_check_user_capability();

	$ip = trim( urldecode( $_REQUEST['ip'] ) );

	// Remove the IP from the option.
	$white_ips = get_site_option( SECUPRESS_WHITE_IP );
	$white_ips = is_array( $white_ips ) ? $white_ips : [];
	if ( ! isset( $white_ips[ $ip ] ) ) {
		secupress_admin_send_message_die( [
			'message' => sprintf( __( 'The IP address %s is not allowed.', 'secupress' ), '<code>' . esc_html( $ip ) . '</code>' ),
			'code'    => 'ip_not_whitelisted',
		] );
	}

	unset( $white_ips[ $ip ] );

	if ( $white_ips ) {
		update_site_option( SECUPRESS_WHITE_IP, $white_ips );
	} else {
		delete_site_option( SECUPRESS_WHITE_IP );
	}

	/**
	 * Fires once a IP is unbanned.
	 *
	 * @since 1.0
	 *
	 * @param (string) $ip      The IP unbanned.
	 * @param (array)  $white_ips The list of IPs banned (keys) and the time they were banned (values).
	 */
	do_action( 'secupress.ip_unallowed', $ip, $white_ips );

	// Send a response.
	secupress_admin_send_message_die( array(
		'message' => sprintf( __( 'The IP address %s has been remove from the list.', 'secupress' ), '<code>' . esc_html( $ip ) . '</code>' ),
		'code'    => 'ip_unwhitelisted',
	) );
}


add_action( 'admin_post_secupress-clear-whitelist-ips', 'secupress_clear_whitelist_ips_ajax_post_cb' );
add_action( 'wp_ajax_secupress-clear-whitelist-ips',    'secupress_clear_whitelist_ips_ajax_post_cb' );
/**
 * Unwhitelist all IP addresses.
 *
 * @since 1.4.9
 */
function secupress_clear_whitelist_ips_ajax_post_cb() {
	// Make all security tests.
	secupress_check_admin_referer( 'secupress-clear-whitelist-ips' );
	secupress_check_user_capability();

	// Remove all IPs from the option.
	delete_site_option( SECUPRESS_WHITE_IP );

	/**
	 * Fires once all IPs are unbanned.
	 *
	 * @since 1.0
	 */
	do_action( 'secupress.ips_cleared' );

	// Send a response.
	secupress_admin_send_message_die( [
		'message' => __( 'All IP addresses have been removed from list.', 'secupress' ),
		'code'    => 'whitelisted_ips_cleared',
	] );
}

// BANNED IPs
add_action( 'admin_post_secupress-ban-ip', 'secupress_ban_ip_ajax_post_cb' );
add_action( 'wp_ajax_secupress-ban-ip',    'secupress_ban_ip_ajax_post_cb' );
/**
 * Ban an IP address.
 *
 * @since 1.0
 */
function secupress_ban_ip_ajax_post_cb() {
	// Make all security tests.
	secupress_check_admin_referer( 'secupress-ban-ip' );
	secupress_check_user_capability();

	if ( empty( $_REQUEST['ip'] ) ) {
		secupress_admin_send_message_die( [
			'message' => __( 'IP address not provided.', 'secupress' ),
			'code'    => 'no_ip',
			'type'    => 'error',
		] );
	}

	// Test the IP.
	$ip          = trim( urldecode( $_REQUEST['ip'] ) );
	$original_ip = $ip;
	$is_list     = false;
	$sep         = "\n";
	$unbanned    = '';
	if ( strpos( $ip, ', ' ) > 0 ) {
		$sep = ', ';
	} elseif ( strpos( $ip, ',' ) > 0 ) {
		$sep = ',';
	} elseif ( strpos( $ip, ';' ) > 0 ) {
		$sep = ';';
	} elseif ( strpos( $ip, ' ' ) > 0 ) {
		$sep = ' ';
	}
	if ( strpos( $ip, $sep ) > 0 ) {
		$is_list = true;
		$ip      = explode( $sep, $ip );
		$count_1 = count( $ip );
		$ip      = array_filter( $ip , function( $_ip ) {
			return secupress_ip_is_valid( $_ip, true );
		} );
		$count_2 = count( $ip );
	}

	if ( ! $is_list && ! secupress_ip_is_valid( $ip, true ) ) {
		secupress_admin_send_message_die( array(
			'message' => __( 'This is not a valid IP address.', 'secupress' ),
			'code'    => 'invalid_ip',
			'type'    => 'error',
		) );
	}

	// Don't ban your IP
	if ( secupress_get_ip() === $ip ) {
		secupress_admin_send_message_die( array(
			'message' => __( 'You cannot ban your own IP address.', 'secupress' ),
			'code'    => 'own_ip',
			'type'    => 'error',
		) );
	}

	// No valid IP in a list
	if ( $is_list && 0 === $count_2 ) {
		secupress_admin_send_message_die( array(
			'message' => __( 'The list does not contains any valid IP address.', 'secupress' ),
			'code'    => 'invalid_ip',
			'type'    => 'error',
		) );
	}

	// Already banned
	$ban_ips = get_site_option( SECUPRESS_BAN_IP );
	$ban_ips = is_array( $ban_ips ) ? $ban_ips : [];
	if ( ! $is_list && isset( $ban_ips[ $ip ] ) ) {
		secupress_admin_send_message_die( array(
			'message' => __( 'This IP is already banned.', 'secupress' ),
			'code'    => 'already_banned',
			'type'    => 'error',
		) );
	}
	// Transform the non list as a list now
	if ( ! is_array( $ip ) ) {
		$ip  = [ $ip ];
	}
	$ip      = array_flip( $ip );
	array_walk( $ip, function( &$_ip, $time ) {
		$_ip = strtotime('+10 years');
	});
	$ban_ips = array_merge( $ban_ips, $ip );

	// Update the ips now
	update_site_option( SECUPRESS_BAN_IP, $ban_ips );

	/* This hook is documented in /inc/functions/admin.php */
	do_action( 'secupress.ban.ip_banned', $ip, $ban_ips );

	$referer_arg  = '&_wp_http_referer=' . urlencode( esc_url_raw( secupress_admin_url( 'modules', 'logs' ) ) );
	$format       = _x( 'M jS Y', 'date', 'secupress' ) . ' ' . _x( 'G:i', 'date', 'secupress' );
	$_time        = date_i18n( $format, strtotime('+10 years'));
	// Send a response.
	if ( ! $is_list ) {
		secupress_admin_send_message_die( [
			'message'    => sprintf( __( 'The IP address %s has been banned.', 'secupress' ) . $unbanned, '<code>' . esc_html( $original_ip ) . '</code>' ),
			'code'       => 'ip_banned',
			'tmplValues' => [
				[
					'ip'        => $original_ip,
					'time'      => $_time,
					'unban_url' => esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=secupress-unban-ip&ip=' . esc_attr( $original_ip ) . $referer_arg ), 'secupress-unban-ip_' . $original_ip ) ),
				],
			] ]
		);
	} else {
		$tmplValues = [];
		foreach ( $ban_ips as $_ip => $time ) {
			$tmplValues[] = [   'ip'        => $_ip,
								'time'      => $_time,
								'unban_url' => esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=secupress-unban-ip&ip=' . esc_attr( $_ip ) . $referer_arg ), 'secupress-unban-ip_' . $_ip ) ),
							];
		}
		if ( $count_1 === $count_2 ) {
			secupress_admin_send_message_die( [
				'message'    => __( 'The IP address list has been banned.' . $unbanned, 'secupress' ),
				'code'       => 'ip_banned',
				'tmplValues' => $tmplValues,
			] );
		} else {
			secupress_admin_send_message_die( [
				'message'    => __( 'Some of the IP address list has been banned.' . $unbanned, 'secupress' ),
				'code'       => 'ip_banned',
				'tmplValues' => $tmplValues,
			] );
		}
	}
}


add_action( 'admin_post_secupress-unban-ip', 'secupress_unban_ip_ajax_post_cb' );
add_action( 'wp_ajax_secupress-unban-ip',    'secupress_unban_ip_ajax_post_cb' );
/**
 * Unban an IP address.
 *
 * @since 1.0
 */
function secupress_unban_ip_ajax_post_cb() {
	// Make all security tests.
	if ( empty( $_REQUEST['ip'] ) ) {
		secupress_admin_send_message_die( array(
			'message' => __( 'IP address not provided.', 'secupress' ),
			'code'    => 'no_ip',
			'type'    => 'error',
		) );
	}

	secupress_check_admin_referer( 'secupress-unban-ip_' . $_REQUEST['ip'] );
	secupress_check_user_capability();

	$ip = urldecode( $_REQUEST['ip'] );

	// Remove the IP from the option.
	$ban_ips = get_site_option( SECUPRESS_BAN_IP );
	$ban_ips = is_array( $ban_ips ) ? $ban_ips : array();

	if ( empty( $ban_ips[ $ip ] ) ) {
		secupress_admin_send_message_die( array(
			'message' => sprintf( __( 'The IP address %s is not banned.', 'secupress' ), '<code>' . esc_html( $ip ) . '</code>' ),
			'code'    => 'ip_not_banned',
		) );
	}

	unset( $ban_ips[ $ip ] );

	if ( $ban_ips ) {
		update_site_option( SECUPRESS_BAN_IP, $ban_ips );
	} else {
		delete_site_option( SECUPRESS_BAN_IP );
	}

	/**
	 * Fires once a IP is unbanned.
	 *
	 * @since 1.0
	 *
	 * @param (string) $ip      The IP unbanned.
	 * @param (array)  $ban_ips The list of IPs banned (keys) and the time they were banned (values).
	 */
	do_action( 'secupress.ban.ip_unbanned', $ip, $ban_ips );

	// Send a response.
	secupress_admin_send_message_die( array(
		'message' => sprintf( __( 'The IP address %s has been unbanned.', 'secupress' ), '<code>' . esc_html( $ip ) . '</code>' ),
		'code'    => 'ip_unbanned',
	) );
}


add_action( 'admin_post_secupress-clear-ips', 'secupress_clear_ips_ajax_post_cb' );
add_action( 'wp_ajax_secupress-clear-ips',    'secupress_clear_ips_ajax_post_cb' );
/**
 * Unban all IP addresses.
 *
 * @since 1.0
 */
function secupress_clear_ips_ajax_post_cb() {
	// Make all security tests.
	secupress_check_admin_referer( 'secupress-clear-ips' );
	secupress_check_user_capability();

	// Remove all IPs from the option.
	delete_site_option( SECUPRESS_BAN_IP );

	/**
	 * Fires once all IPs are unbanned.
	 *
	 * @since 1.0
	 */
	do_action( 'secupress.ban.ips_cleared' );

	// Send a response.
	secupress_admin_send_message_die( array(
		'message' => __( 'All IP addresses have been unbanned.', 'secupress' ),
		'code'    => 'banned_ips_cleared',
	) );
}


add_action( 'admin_post_secupress_reset_settings', 'secupress_admin_post_reset_settings_post_cb' );
/**
 * Reset SecuPress settings or module settings.
 *
 * @since 1.4.4 Params $module & $bypass.
 * @since 1.0
 *
 * @author Julio Potier
 * @param (string) $module Empty by default, the module to be reset if $_GET is not defined.
 * @param (bool)   $bypass False by default, if true, the security will not be checked, already done by the caller.
 */
function secupress_admin_post_reset_settings_post_cb( $module = '', $bypass = false ) {
	if ( empty( $_GET['module'] ) && ! $module ) {
		secupress_admin_die();
	}

	$module = isset( $_GET['module'] ) ? $_GET['module'] : $module;
	if ( ! $bypass ) {
		// Make all security tests.
		secupress_check_admin_referer( 'secupress_reset_' . $module );
		secupress_check_user_capability();
	}

	secupress_delete_module_option( $module );
	/** This action is documented in inc/admin/upgrader.php */
	do_action( 'secupress.first_install', $module );

	if ( ! $bypass ) {
		secupress_add_transient_notice( __( 'Module settings reset.', 'secupress' ), 'updated', 'module-reset' );

		wp_safe_redirect( esc_url_raw( secupress_admin_url( 'modules', $module ) ) );
		die();
	}
}


add_action( 'wp_ajax_sanitize_move_login_slug', 'secupress_sanitize_move_login_slug_ajax_post_cb' );
/**
 * Sanitize a value for a Move Login slug.
 *
 * @since 1.2.5
 * @author Grégory Viguier
 */
function secupress_sanitize_move_login_slug_ajax_post_cb() {
	// Make all security tests.
	secupress_check_admin_referer( 'sanitize_move_login_slug' );
	secupress_check_user_capability();

	if ( empty( $_GET['default'] ) || ! isset( $_GET['slug'] ) ) {
		wp_send_json_error();
	}

	$default = sanitize_title( $_GET['default'] );

	if ( ! $default ) {
		wp_send_json_error();
	}

	if ( 'login' === $default ) {
		$slug = sanitize_title( $_GET['slug'], '', 'display' );
		// See secupress/inc/modules/users-login/settings/move-login.php.
		$slug = $slug ? $slug : '##-' . strtoupper( sanitize_title( __( 'Choose your login URL', 'secupress' ), '', 'display' ) ) . '-##';
	} else {
		$slug = sanitize_title( $_GET['slug'], $default, 'display' );
	}

	wp_send_json_success( $slug );
}


add_action( 'admin_post_nopriv_secupress_unlock_admin', 'secupress_unlock_admin_ajax_post_cb' );
/**
 * Send an unlock email if the provided address is from an admin
 *
 * @author Julio Potier
 * @since 1.3.2
 **/
function secupress_unlock_admin_ajax_post_cb() {
	$message = __( 'If this email address exists, an email will be sent to it to unlock your account.', 'secupress' );
	if ( ! isset( $_POST['_wpnonce'], $_POST['email'] ) || ! is_email( $_POST['email'] ) || ! check_ajax_referer( 'secupress-unban-ip-admin', '_wpnonce' ) ) { // WPCS: CSRF ok.
		secupress_die( $message, __( 'Email', 'secupress' ), array( 'force_die' => true ) );
	}
	$_CLEAN          = [];
	$_CLEAN['email'] = is_email( $_POST['email'] );
	if ( ! $_CLEAN['email'] ) {
		secupress_die( $message, __( 'Email', 'secupress' ), array( 'force_die' => true ) );
	}
	$user            = get_user_by( 'email', $_CLEAN['email'] );
	$capa            = secupress_get_capability( true, 'unlock_administrator' );
	if ( ! secupress_is_user( $user ) || ! user_can( $user, $capa ) ) {
		secupress_die( $message, __( 'Email', 'secupress' ), array( 'force_die' => true ) );
	}
	$url_remember = wp_login_url();

	$subject      = sprintf( __( '[%s] Unlock a lost user', 'secupress' ), '###SITENAME###' );
	$message      = sprintf( __( 'Hello %1$s,
It seems you are locked out from the website ###SITENAME###.

You can now follow this link to your login page (remember it now!):
%2$s

Have a nice day !

Regards,
All at ###SITENAME###
###SITEURL###', 'secupress' ),
							$user->display_name,
							$url_remember
					);

	$capa             = secupress_get_capability(); // Do it again to get the usual capa for managing options.
	if ( $capa && apply_filters( 'secupress.plugins.move_login.email.deactivation_link', true ) ) {
		$token        = md5( secupress_generate_key() );
		$url_remove   = add_query_arg( [ '_wpnonce' => $token, 'user_email' => $user->user_email ], admin_url( 'admin-post.php?action=secupress_deactivate_module&module=move-login' ) );
		set_transient( 'secupress_unlock_admin_key-' . $user->user_email, $token, HOUR_IN_SECONDS );
		$message     .= "\n" . sprintf( __( "ps: you can also deactivate the Move Login module:\n%s", 'secupress' ), $url_remove . ' ' . __( '(Valid 1 hour)', 'secupress' ) );
	}

	/**
	 * Filter the mail subject
	 * @param (string) $subject
	 * @param (WP_User) $user
	 * @since 2.2
	 */
	$subject = apply_filters( 'secupress.mail.unlock_administrator.subject', $subject, $user );
	/**
	 * Filter the mail message
	 * @param (string) $message
	 * @param (WP_User) $user
	 * @param (string) $url_remove
	 * @param (string) $url_remember
	 * @since 2.2
	 */
	$message = apply_filters( 'secupress.mail.unlock_administrator.message', $message, $user, $url_remove, $url_remember );


	$sent = secupress_send_mail( $_CLEAN['email'], $subject, $message );
	secupress_die( $message, __( 'Email', 'secupress' ), array( 'force_die' => true ) );
}

add_action( 'admin_post_nopriv_secupress_deactivate_module', 'secupress_deactivate_module_admin_post_cb' );
/**
 * Can deactivate a module from a link sent by secupress_unlock_admin_ajax_post_cb()
 *
 * @since 1.3.2
 * @author Julio Potier
 **/
function secupress_deactivate_module_admin_post_cb() {
	$tr_key_name = 'secupress_unlock_admin_key-' . ( isset( $_GET['user_email'] ) ? $_GET['user_email'] : '' );
	if ( ! isset( $_GET['_wpnonce'], $_GET['module'], $_GET['user_email'] ) || 
		empty( $_GET['_wpnonce'] ) || 
		! get_transient( $tr_key_name ) || 
		! hash_equals( get_transient( $tr_key_name ), $_GET['_wpnonce'] )
	) {
		delete_transient( $tr_key_name );
		wp_die( 'Something went wrong.' );
	}
	delete_transient( $tr_key_name );
	secupress_deactivate_submodule( 'users-login', array( 'move-login' ) );
	wp_redirect( wp_login_url( secupress_admin_url( 'modules', 'users-login' ) ) );
	die();
}

add_action( 'admin_post_secupress_reset_all_settings', 'secupress_reset_all_settings_admin_post_cb' );
/**
 * Will reset the settings like a fresh install
 *
 * @since 1.4.4
 * @author Julio Potier
 **/
function secupress_reset_all_settings_admin_post_cb() {
	if ( ! isset( $_GET['_wpnonce'] ) ) {
		wp_die( 'Something went wrong.' );
	}

	secupress_check_admin_referer( 'secupress_reset_all_settings' );
	secupress_check_user_capability();

	$modules = secupress_get_modules();
	foreach ( $modules as $key => $module ) {

		if ( isset( $module['with_reset_box'] ) && false === $module['with_reset_box'] ) {
			continue;
		}
		secupress_admin_post_reset_settings_post_cb( $key, true );
	}

	secupress_add_transient_notice( __( 'All modules settings reset', 'secupress' ), 'updated', 'module-reset' );

	wp_safe_redirect( wp_get_referer() );
	die();
}

add_action( 'wp_ajax_secupress_set_scan_speed', 'secupress_set_scan_speed_admin_post_cb' );
/**
 * Set scanner speed
 *
 * @since 1.4.4
 * @author Julio Potier
 **/
function secupress_set_scan_speed_admin_post_cb() {
	$old_value       = secupress_get_option( 'scan-speed', 0 );
	$allowed_values  = [ 'max' => 0, 'normal' => 250, 'low' => 1000 ];
	$_clean          = [];
	$_clean['text']  = isset( $allowed_values[ $_GET['value'] ] ) ? $_GET['value'] : 'max';
	$_clean['value'] = isset( $allowed_values[ $_GET['value'] ] ) ? $allowed_values[ $_GET['value'] ] : 0;

	if ( ! isset( $_GET['_wpnonce'], $_GET['value'] ) || ! check_ajax_referer( 'secupress-set-scan-speed', '_wpnonce', false ) ) {
		$allowed_values = array_flip( $allowed_values );
		wp_send_json_error( [ 'val' => $old_value, 'text' => $allowed_values[ $old_value ] ] );
	}

	/**
	* Filter the milliseconds between scans.
	*
	* @param (int) $value Defaults values are 0, 250 (1/4 sec), 1000 (1 sec)
	* @since 1.4.5
	* @author Julio Potier
	*/
	$value = apply_filters( 'secupress.scanner.scan-speed', $_clean['value'] );
	secupress_set_option( 'scan-speed', $value );
	wp_send_json_success( [ 'val' => $_clean['value'], 'text' => $_clean['text'] ] );
}


// add_action( 'wp_ajax_secupress_send_deactivation_info', 'secupress_send_deactivation_info_admin_post_cb' );
/**
 * Send the deactivation reason on secupress.me
 *
 * @since 2.0
 * @author Julio Potier
 *
 * @return (string) json
 **/
function secupress_send_deactivation_info_admin_post_cb() {
	if ( ! isset( $_GET['nonce'], $_GET['reason'] ) || ! wp_verify_nonce( $_GET['nonce'], 'deactivation-info' ) ) {
		wp_send_json_error();
	}
	set_site_transient( 'secupress-deactivation-form', 1, HOUR_IN_SECONDS );
	$args = [
		'timeout'    => 0.01,
		'blocking'   => false,
		'body'       => esc_html( $_GET['reason'] )
	];
	wp_remote_post( SECUPRESS_WEB_MAIN . 'api/reason.php', $args );
	wp_send_json_success();
}

add_action( 'wp_ajax_secupress_malwareScanStatus', 'secupress_get_malwarescastatus_admin_post_cb' );
/**
 * Return the current scanned folders
 *
 * @since 2.0
 * @author Julio Potier
 *
 * @return (string)
 **/
function secupress_get_malwarescastatus_admin_post_cb() {
	$response                      = [];
	if ( ! isset( $_GET['_wpnonce'] ) || ! check_ajax_referer( 'secupress_malwareScanStatus', '_wpnonce', false ) ) {
		$response['malwareScanStatus'] = false;
		wp_send_json_error( $response );
	}
	$response['malwareScanStatus'] = ! secupress_file_monitoring_get_instance()->is_monitoring_running();
	$response['currentItems']      = get_site_transient( SECUPRESS_FULL_FILETREE ) !== false ? array_map( function( $val ) { return str_replace( ABSPATH, '/', $val ); }, get_site_transient( SECUPRESS_FULL_FILETREE ) ) : [];
	wp_send_json_success( $response );
}

add_action( 'admin_post_secupress-regen-keys', 'secupress_regen_hash_key_admin_post_cb' );
/**
 * Set a new has_key, and reset the salt keys too
 *
 * @since 2.3.17 Regen the file too
 * @since 2.0
 * @author Julio Potier
 **/
function secupress_regen_hash_key_admin_post_cb() {
	global $current_user;
	
	if ( ! secupress_is_submodule_active( 'wordpress-core', 'wp-config-constant-saltkeys' ) ) {
		wp_die( 'Something went wrong.' );
	}

	if ( ! isset( $_GET['_wpnonce'] ) || ! check_ajax_referer( 'secupress-regen-keys', '_wpnonce', false ) ) {
		wp_die( 'Something went wrong.' );
	}
	// Do not use secupress_get_option() here.
	$options             = get_site_option( SECUPRESS_SETTINGS_SLUG );
	$options['hash_key'] = secupress_generate_key( 64 );
	secupress_update_options( $options );
	// We do not need to refresh the hashes in the file, do it only if file is missing
	if ( ! defined( 'SECUPRESS_SALT_KEYS_MODULE_ACTIVE' ) ) {
		$filesystem  = secupress_get_filesystem();
		$alicia_keys = $filesystem->get_contents( SECUPRESS_INC_PATH . 'data/salt-keys.phps' );
		$args        = [
			'{{PLUGIN_NAME}}'  => SECUPRESS_PLUGIN_NAME,
			'{{HASH1}}'        => wp_generate_password( 64, true, true ),
			'{{HASH2}}'        => wp_generate_password( 64, true, true ),
		];
		$alicia_keys = str_replace( array_keys( $args ), $args, $alicia_keys );
		secupress_create_mu_plugin( 'salt_keys', $alicia_keys, uniqid() );
	}

	secupress_auto_login( 'Salt_Keys', null, __( 'Security keys have been successfully regenerated.', 'secupress' ) );
}

add_action( 'admin_post_secupress_accept_notification', 'secupress_accept_notification_admin_post_cb' );
/**
 * Validate the Slack Notification
 *
 * @since 2.0
 * @author Julio Potier
 *
 **/
function secupress_accept_notification_admin_post_cb() {
	if ( ! isset( $_GET['_wpnonce'], $_GET['type'] ) || ! check_ajax_referer( 'secupress_accept_notification-type-' . $_GET['type'] ) ) {
		wp_die( 'Something went wrong.' );
	}
	secupress_set_option( 'notification-types_' . $_GET['type'], secupress_get_module_option( 'notification-types_slack', false, 'alerts' ) ); // WPCS: XSS Ok.
	wp_redirect( secupress_admin_url( 'modules', 'alerts#row-notification-types_slack' ) );
	die();
}


add_action( 'wp_ajax_dismiss-sp-pointer', 'secupress_dismiss_pointer_admin_post_cb' );
/**
 * Dismiss our pointers
 *
 * @since 2.0
 * @author Julio Potier
 *
 * @return (string) JSON
 **/
function secupress_dismiss_pointer_admin_post_cb( $_pointer = '' ) {
	$pointer = isset( $_POST['pointer'] ) ? sanitize_key( $_POST['pointer'] ) : $_pointer;

	if ( ! $_pointer && ( ! $pointer || ! check_ajax_referer( 'dismiss-pointer_' . $pointer, '_ajaxnonce', false ) ) ) {
		wp_send_json_error();
	}

	$dismissed = array_filter( explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) );

	if ( ! $_pointer && in_array( $pointer, $dismissed, true ) ) {
		wp_send_json_error();
	}

	$dismissed[] = $pointer;
	$dismissed   = implode( ',', $dismissed );

	update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed );
	if ( ! $_pointer ) {
		wp_send_json_success();
	}
}

//// add_action( 'admin_post_http_log_actions', 'secupress_http_log_actions_admin_post_cb' );
function secupress_http_log_actions_admin_post_cb() {
	if ( ! isset( $_POST['_wpnonce'], $_POST['log_id'], $_POST['http_log'] ) || ! check_admin_referer( 'http_log_actions' . $_POST['log_id'] ) ) {
		die( '0' );
	}
	$http_logs = get_option( SECUPRESS_HTTP_LOGS );
	$http_logs = is_array( $http_logs ) ? $http_logs : [];
	$options   = isset( $_POST['http_log']['options'] ) ? $_POST['http_log']['options'] : [];
	unset( $_POST['http_log']['options'] );
	$_CLEAN    = $_POST['http_log'];
	foreach ( $_CLEAN as $url => $values ) {
		if ( empty( $values['index'] ) || '1' === $values['index'] ) {
			unset( $_CLEAN[ $url ] );
			continue;
		}

		$parsed_url         = shortcode_atts( [ 'scheme' => '', 'host' => '', 'path' => '', 'query' => '' ], wp_parse_url( $url ) );
		if ( empty( $parsed_url['scheme'] ) ) {
			unset( $_CLEAN[ $url ] );
			continue;
		}
		if ( ! empty( $parsed_url['query'] ) ) {
			parse_str( html_entity_decode( $parsed_url['query'] ), $get_params );
			if ( ! empty( $options['ignore-param'] ) ) {
				$get_params = array_diff_key( $get_params, array_flip( $options['ignore-param'] ) );
			}
			ksort( $get_params );
			$path_name      = $parsed_url['scheme'] . '://' . untrailingslashit( $parsed_url['host'] ) . $parsed_url['path'];
			$query          = http_build_query( $get_params );
			$query          = ! empty( $query ) ? '?' . $query : '';
			$temp           = $_CLEAN[ $url ];
			unset( $_CLEAN[ $url ] );
			$url            = $path_name . $query;
			$_CLEAN[ $url ] = $temp;
		}
		if ( isset( $options ) ) {
			$_CLEAN[ $url ]['options'] = $options;
			unset( $options );
		}

		if ( ! isset( $http_logs[ $url ]['since'] ) ) {
			$_CLEAN[ $url ]['since'] = time();
		}
		if ( ! isset( $http_logs[ $url ]['hits'] ) ) {
			$_CLEAN[ $url ]['hits']  = 0;
		}
		// Always reset the last call on save/update.
		$_CLEAN[ $url ]['last']      = 0;
	}
	$http_logs = array_merge( $http_logs, $_CLEAN );
	ksort( $http_logs );
	update_option( SECUPRESS_HTTP_LOGS, $http_logs, false );
	wp_safe_redirect( wp_get_referer() );
	die();
}

add_action( 'wp_ajax_secupress_check_malware_plugin', 'secupress_check_malware_plugin_admin_post_cb' );
/**
 * 
 *
 * @since 2.2.6
 * @author Julio Potier
 *
 * @return (string) JSON
 **/
function secupress_check_malware_plugin_admin_post_cb() {
	if ( ! isset( $_POST['plugin'], $_POST['muplugin'], $_POST['_ajax_nonce'] ) || ! check_ajax_referer( 'secupress_check_malware_plugin' ) ) {
		wp_send_json_error( 'Incorrect AJAX Request' );
	}
	$res              = '';
	$yes              = '<span class="dashicons dashicons-yes-alt"></span>';
	$toggle           = '<span class="dashicons secupress-dashicon dashicons-arrow-right-alt2"></span>';
	if ( '1' === $_POST['muplugin'] ) {
		$plugin_file  = basename( $_POST['plugin'] );
		$root_path    = MUPLUGINDIR;
		$tr_name      = 'secupress-check-malware-result-mu-' . md5( $plugin_file );
		$malware      = secupress_check_malware( $root_path . '/' . $plugin_file, true );
		if ( $malware ) {
			$res .= $toggle;
			$res .= '<code>/' . $plugin_file . '</code>';
			$res .= $malware;
		} else {
			$res  = $yes;
		}
	} else { 
		$flag         = false;
		$plugin_file  = plugin_basename( $_POST['plugin'] );
		$root_path    = WP_PLUGIN_DIR . '/' . dirname( $plugin_file );
		$plugin_files = secupress_list_files_from_folder( $root_path );
		foreach( $plugin_files as $p_file ) {
			$tr_name  = 'secupress-check-malware-result-p-' . md5( $root_path );
			$malware  = secupress_check_malware( str_replace( ABSPATH, '', $root_path . '/' . $p_file ) );
			if ( $malware ) {
				$flag = true;
				$res .= $toggle;
				$res .= '<code>/' . dirname( $plugin_file ) . '/' . $p_file . '</code>';
				$res .= $malware;
				$res .= '<br>';
			}
		}
		if ( ! $flag ) {
			 $res     = $yes;
		}
	}
	set_transient( $tr_name, $res, DAY_IN_SECONDS );

	wp_send_json_success( $res );
}


add_action( 'wp_ajax_secupress_search', 'secupress_search_ajax_cb' );
/**
 * Used to handle search requests in modules page.
 *
 * @author Julio Potier
 * @since 2.6
 *
 * @return (string) JSON
 */
function secupress_search_ajax_cb() {
	secupress_check_user_capability();
	secupress_check_admin_referer( 'secupress_search', 'secupress_search_nonce' );

	$search_query = isset( $_POST['secupress_module_search'] ) ? trim( $_POST['secupress_module_search'] ) : '';

	if ( empty( $search_query ) || strlen( $search_query ) < 2 ) {
		wp_send_json_success( [] );
	}

	$results        = [];
	$results_by_key = [];
	$modules        = secupress_get_modules();
	$search_query   = function_exists( 'mb_strtolower' ) ? mb_strtolower( $search_query ) : strtolower( $search_query );

	foreach ( $modules as $module_key => $module_data ) {
		if ( empty( $module_data['submodules'] ) || ! is_array( $module_data['submodules'] ) ) {
			continue;
		}

		foreach ( $module_data['submodules'] as $submodule_key => $submodule_title ) {
			if ( empty( $submodule_title ) ) {
				continue;
			}

			$clean_title       = preg_replace( '/^[>*]+/', '', $submodule_title );
			$clean_title       = trim( wp_strip_all_tags( $clean_title ) );
			$clean_title_lower = function_exists( 'mb_strtolower' ) ? mb_strtolower( $clean_title ) : strtolower( $clean_title );
			$title_words       = preg_split( '/\s+/', $clean_title_lower, -1, PREG_SPLIT_NO_EMPTY );
			$best_score        = 0;
			$has_exact_match   = false;

			foreach ( $title_words as $title_word ) {
				if ( $title_word === $search_query ) {
					$has_exact_match = true;
				}
				$has_word_match    = false !== ( function_exists( 'mb_strpos' ) ? mb_strpos( $title_word, $search_query ) : strpos( $title_word, $search_query ) );
				$levenshtein_score = function_exists( 'secupress_levenshtein' ) ? secupress_levenshtein( $title_word, $search_query ) : 0;
				$prefix_len        = 0;
				$max_prefix_len    = min( strlen( $title_word ), strlen( $search_query ) );
				for ( $i = 0; $i < $max_prefix_len; $i++ ) {
					if ( $title_word[$i] !== $search_query[$i] ) {
						break;
					}
					$prefix_len++;
				}
				$prefix_score   = $prefix_len >= 3 ? 0.7 : 0;
				$word_score     = $has_word_match ? 1 : max( $levenshtein_score, $prefix_score );
				if ( $word_score > $best_score ) {
					$best_score = $word_score;
				}
			}

			if ( $best_score >= 0.7 ) {
				$url            = secupress_admin_url( 'modules', $module_key ) . '#' . $submodule_key;
				$result_key     = $module_key . '|' . $submodule_key;
				if ( ! isset( $results_by_key[ $result_key ] ) || $best_score > $results_by_key[ $result_key ]['score'] ) {
					$results_by_key[ $result_key ] = [
						'title' => $clean_title,
						'url'   => $url,
						'score' => $best_score,
						'exact' => $has_exact_match ? 1 : 0,
					];
				}
			}
		}
	}

	if ( $results_by_key ) {
		$results = array_values( $results_by_key );
		usort( $results, function( $left, $right ) {
			if ( $left['exact'] !== $right['exact'] ) {
				return ( $left['exact'] > $right['exact'] ) ? -1 : 1;
			}
			if ( $left['score'] === $right['score'] ) {
				return 0;
			}
			return ( $left['score'] > $right['score'] ) ? -1 : 1;
		} );
		foreach ( $results as $index => $result ) {
			unset( $results[ $index ]['score'] );
			unset( $results[ $index ]['exact'] );
		}
	}

	wp_send_json_success( $results );
}
