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;
}
}