HEX
Server: LiteSpeed
System: Linux server315.web-hosting.com 4.18.0-553.54.1.lve.el8.x86_64 #1 SMP Wed Jun 4 13:01:13 UTC 2025 x86_64
User: globfdxw (6114)
PHP: 8.1.34
Disabled: NONE
Upload Files
File: /home/globfdxw/www/wp-content/plugins/wpforms-surveys-polls/src/Reporting/Ajax.php
<?php

namespace WPFormsSurveys\Reporting;

use WPFormsSurveys\Reporting\Helpers as ReportingHelpers;

/**
 * Survey reporting admin page and related functionality.
 *
 * @since 1.0.0
 */
class Ajax {

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {

		$this->init();
	}

	/**
	 * Initialize.
	 *
	 * @since 1.0.0
	 */
	public function init() {

		$this->hooks();
	}

	/**
	 * Register hooks.
	 *
	 * @since 1.18.0
	 */
	private function hooks() {

		// Register AJAX callbacks.
		add_action( 'wp_ajax_wpforms_surveys_field_data', [ $this, 'survey_get_field_data' ] );
		add_action( 'wp_ajax_wpforms_surveys_set_preview_field', [ $this, 'survey_set_preview_field' ] );
		add_action( 'wp_ajax_wpforms_surveys_graph_filter_save_preset', [ $this, 'survey_filter_save_preset' ] );
		add_action( 'wp_ajax_wpforms_surveys_graph_filter_delete_preset', [ $this, 'survey_filter_delete_preset' ] );
	}

	/**
	 * Get survey data for a given field.
	 *
	 * @since 1.0.0
	 * @since 1.15.0 Added caching and permissions check.
	 */
	public function survey_get_field_data() {

		// Run a security check.
		check_ajax_referer( 'wpforms-admin', 'nonce' );

		$fetch_error = __( 'Error fetching the data.', 'wpforms-surveys-polls' );

		if ( empty( $_POST['form_id'] ) || empty( $_POST['field_ids'] ) || ! isset( $_POST['entry_count'] ) ) {
			wp_send_json_error( [ 'message' => esc_html( $fetch_error ) ] );
		}

		$form_id = absint( $_POST['form_id'] );

		// If filters are provided, pass them into the request scope so downstream code can use them.
		if ( ! empty( $_POST['filters'] ) ) {
			// phpcs:disable WordPress.Security.NonceVerification.Recommended
			$raw_filters         = json_decode( wp_unslash( $_POST['filters'] ), true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$sanitized           = is_array( $raw_filters ) ? ReportingHelpers::sanitize_filters_array( $raw_filters ) : [];
			$_REQUEST['filters'] = ReportingHelpers::ensure_array_fields( $sanitized );
			// phpcs:enable WordPress.Security.NonceVerification.Recommended
		}

		// Check for permissions.
		if ( ! wpforms_current_user_can( 'view_entries_form_single', $form_id ) ) {
			wp_send_json_error( [ 'message' => esc_html( $fetch_error ) ] );
		}

		$form_data = wpforms()->obj( 'form' )->get(
			$form_id,
			[
				'content_only' => true,
				'cap'          => 'view_entries_form_single',
			]
		);

		if ( empty( $form_data ) ) {
			wp_send_json_error( [ 'message' => esc_html( $fetch_error ) ] );
		}

		$field_ids   = array_map( 'absint', (array) $_POST['field_ids'] );
		$entry_count = absint( $_POST['entry_count'] );
		$fields      = $this->prepare_fields_data( $field_ids, $form_data, $form_id, $entry_count );

		// Reset the original array keys to preserve the intended order,
		// as JavaScript may reorder object keys, especially numeric ones.
		wp_send_json_success( array_values( $fields ) );
	}

	/**
	 * Prepare survey field data.
	 *
	 * @since 1.15.0
	 *
	 * @param array $field_ids   Fields ids.
	 * @param array $form_data   Form data and settings.
	 * @param int   $form_id     Form ID.
	 * @param int   $entry_count Entry count.
	 *
	 * @return array
	 */
	private function prepare_fields_data( array $field_ids, array $form_data, int $form_id, int $entry_count ): array {

		$fields   = [];
		$field_id = '';

		foreach ( $field_ids as $field_id ) {
			if ( ! isset( $form_data['fields'][ $field_id ] ) ) {
				continue;
			}

			$fields[ $field_id ] = Fields::get_survey_field_data( $form_data['fields'][ $field_id ], $form_id, $entry_count );
		}

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

		/**
		 * Allow caching of survey report data.
		 *
		 * @since 1.0.0
		 *
		 * @param bool $is_cache_enabled Whether to cache survey report data.
		 * @param int  $form_id          Form ID.
		 */
		if ( (bool) apply_filters( 'wpforms_surveys_polls_report_caching', true, $form_id ) ) { // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName

			$base_key = count( $fields ) === 1 ? "wpforms_survey_report_{$form_id}_{$entry_count}_$field_id" : "wpforms_survey_report_{$form_id}_$entry_count";

			// Make cache key filter-aware to avoid mixing filtered and unfiltered datasets.
			$filter_hash = '';

			// phpcs:disable WordPress.Security.NonceVerification.Recommended
			if ( ! empty( $_REQUEST['filters'] ) && is_array( $_REQUEST['filters'] ) ) {
				$input       = ReportingHelpers::ensure_array_fields( wp_unslash( $_REQUEST['filters'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
				$normalized  = ReportingHelpers::normalize_filters( $input );
				$filter_hash = '_' . substr( wp_hash( wp_json_encode( $normalized ) ), 0, 12 );
			}
			// phpcs:enable WordPress.Security.NonceVerification.Recommended

			$cache_key = $base_key . $filter_hash;

			// Reset the original array keys to preserve the intended order,
			// as JavaScript may reorder object keys, especially numeric ones.
			set_transient( $cache_key, wp_json_encode( array_values( $fields ) ), DAY_IN_SECONDS * 2 );
		}

		return $fields;
	}

	/**
	 * Set field data cache.
	 *
	 * @since 1.0.0
	 * @deprecated 1.15.0
	 */
	public function survey_set_field_cache() {

		_deprecated_function( __METHOD__, '1.15.0 of the WPForms Surveys and Polls addon.' );

		// Run a security check.
		check_ajax_referer( 'wpforms-admin', 'nonce' );

		if ( empty( $_POST['form_id'] ) || empty( $_POST['entry_count'] ) || empty( $_POST['field_data'] ) ) {
			wp_send_json_error();
		}

		$form_id = absint( $_POST['form_id'] );

		// Check for permissions.
		if ( ! wpforms_current_user_can( 'view_entries_form_single', $form_id ) ) {
			wp_send_json_error();
		}

		$entry_count = absint( $_POST['entry_count'] );
		$field_id    = ! empty( $_POST['field_id'] ) ? absint( $_POST['field_id'] ) : false;

		$form_data = wpforms()->obj( 'form' )->get(
			$form_id,
			[
				'content_only' => true,
				'cap'          => 'view_entries_form_single',
			]
		);

		if ( ! is_array( $form_data ) ) {
			wp_send_json_error();
		}

		$this->prepare_fields_data( [ $field_id ], $form_data, $form_id, $entry_count );
	}

	/**
	 * Set preferred survey preview field.
	 *
	 * @since 1.0.0
	 */
	public function survey_set_preview_field() {

		// Run a security check.
		check_ajax_referer( 'wpforms-admin', 'nonce' );

		if ( empty( $_POST['form_id'] ) || empty( $_POST['field_id'] ) ) {
			wp_send_json_error();
		}

		$form_id = absint( $_POST['form_id'] );

		// Check for permissions.
		if ( ! wpforms_current_user_can( 'view_entries_form_single', $form_id ) ) {
			wp_send_json_error();
		}

		$field_id = absint( $_POST['field_id'] );

		// Update form meta.
		wpforms()->obj( 'form' )->update_meta( $form_id, 'survey_preview', $field_id );

		wp_send_json_success();
	}

	/**
	 * Save a Filters preset (create or update).
	 *
	 * Stores presets in an option and returns the preset ID.
	 *
	 * @since 1.18.0
	 */
	public function survey_filter_save_preset() {
		// Security.
		check_ajax_referer( 'wpforms-admin', 'nonce' );

		$params = $this->get_preset_request_params();

		$this->validate_preset_permission( $params['form_id'], $params['name'] );

		$filters = $this->parse_and_sanitize_filters( $params['raw_filters'] );

		$result = $this->upsert_preset( $params['form_id'], $params['name'], $filters, $params['id'] );

		wp_send_json_success( $result );
	}

	/**
	 * Read and sanitize request params for saving a preset.
	 *
	 * @since 1.18.0
	 *
	 * @return array
	 */
	private function get_preset_request_params(): array {
		// phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
		$form_id     = isset( $_POST['form_id'] ) ? absint( $_POST['form_id'] ) : 0;
		$name        = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
		$raw_filters = isset( $_POST['filters'] ) ? wp_unslash( $_POST['filters'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$id          = isset( $_POST['id'] ) ? sanitize_text_field( wp_unslash( $_POST['id'] ) ) : '';
		// phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended

		return [
			'form_id'     => $form_id,
			'name'        => $name,
			'raw_filters' => $raw_filters,
			'id'          => $id,
		];
	}

	/**
	 * Validate permissions and required fields for presets.
	 *
	 * Sends JSON error and exits when validation fails.
	 *
	 * @since 1.18.0
	 *
	 * @param int    $form_id Form ID.
	 * @param string $name    Preset name.
	 */
	private function validate_preset_permission( int $form_id, string $name ): void {

		if ( ! $form_id || ! wpforms_current_user_can( 'view_entries_form_single', $form_id ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to perform this action.', 'wpforms-surveys-polls' ) ] );
		}

		if ( $name === '' ) {
			wp_send_json_error( [ 'message' => esc_html__( 'Please provide a preset name.', 'wpforms-surveys-polls' ) ] );
		}
	}

	/**
	 * Parse incoming filters and sanitize them recursively.
	 *
	 * @since 1.18.0
	 *
	 * @param mixed $raw Raw filters payload (JSON string or array).
	 *
	 * @return array Sanitized filters array.
	 */
	private function parse_and_sanitize_filters( $raw ): array {

		$filters = [];

		if ( is_string( $raw ) && $raw !== '' ) {
			$decoded = json_decode( $raw, true );
			$filters = is_array( $decoded ) ? $decoded : [];
		} elseif ( is_array( $raw ) ) {
			$filters = $raw;
		}

		return $this->sanitize_filters_array( $filters );
	}

	/**
	 * Create or update a preset and persist it into options storage.
	 *
	 * @since 1.18.0
	 *
	 * @param int    $form_id Form ID.
	 * @param string $name    Preset name.
	 * @param array  $filters Sanitized filters.
	 * @param string $id      Existing preset ID or empty for the new one.
	 *
	 * @return array Result payload to return to the client.
	 */
	private function upsert_preset( int $form_id, string $name, array $filters, string $id ): array {

		$option_key = 'wpforms_survey_filter_presets';
		$presets    = (array) get_option( $option_key, [] );

		// If updating an existing preset, ensure it belongs to the same form.
		if ( $id !== '' && isset( $presets[ $id ] ) ) {
			$this->verify_preset_form_id( $presets, $id, $form_id );
		}

		if ( $id === '' ) {
			$id = uniqid( 'sp_', true );
		}

		$presets[ $id ] = [
			'id'      => $id,
			'name'    => $name,
			'filters' => $filters,
			'form_id' => $form_id,
		];

		update_option( $option_key, $presets );

		return [
			'id'   => $id,
			'name' => $name,
		];
	}

	/**
	 * Delete a Filters preset.
	 *
	 * @since 1.18.0
	 */
	public function survey_filter_delete_preset() {
		// Security.
		check_ajax_referer( 'wpforms-admin', 'nonce' );

		// phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended -- nonce verified by callers.
		$form_id = isset( $_POST['form_id'] ) ? absint( $_POST['form_id'] ) : 0;
		$id      = isset( $_POST['id'] ) ? sanitize_text_field( wp_unslash( $_POST['id'] ) ) : '';
		// phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended

		if ( $id === '' || ! $form_id || ! wpforms_current_user_can( 'view_entries_form_single', $form_id ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to perform this action.', 'wpforms-surveys-polls' ) ] );
		}

		$option_key = 'wpforms_survey_filter_presets';
		$presets    = (array) get_option( $option_key, [] );

		if ( isset( $presets[ $id ] ) ) {
			$this->verify_preset_form_id( $presets, $id, $form_id );
			unset( $presets[ $id ] );
			update_option( $option_key, $presets );
		}

		wp_send_json_success();
	}

	/**
	 * Verify that the preset belongs to the same form.
	 *
	 * @since 1.18.0
	 *
	 * @param array  $presets Array of presets.
	 * @param string $id      Preset ID.
	 * @param int    $form_id Form ID.
	 */
	private function verify_preset_form_id( array $presets, string $id, int $form_id ): void {

		$existing_form_id = isset( $presets[ $id ]['form_id'] ) ? absint( $presets[ $id ]['form_id'] ) : 0;

		if ( $existing_form_id === $form_id ) {
			return;
		}

		wp_send_json_error(
			[
				'message' => esc_html__( 'You are not allowed to modify this preset.', 'wpforms-surveys-polls' ),
			]
		);
	}

	/**
	 * Sanitize filters array keys/values recursively.
	 *
	 * @since 1.18.0
	 *
	 * @param mixed $value Filters value.
	 *
	 * @return mixed
	 */
	private function sanitize_filters_array( $value ) {

		if ( is_array( $value ) ) {
			$clean = [];

			foreach ( $value as $k => $v ) {
				$clean[ sanitize_key( (string) $k ) ] = $this->sanitize_filters_array( $v );
			}

			return $clean;
		}

		if ( is_numeric( $value ) ) {
			return (int) $value;
		}

		if ( is_string( $value ) ) {
			return sanitize_text_field( $value );
		}

		return $value;
	}
}