Skip to content

Commit 06c5d76

Browse files
committed
Use rest endpoint instead of ajax
1 parent 3e80bb4 commit 06c5d76

File tree

4 files changed

+180
-30
lines changed

4 files changed

+180
-30
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: minor
2+
Type: added
3+
4+
Add REST API endpoint for exporting form responses, replacing legacy AJAX implementation

projects/packages/forms/src/contact-form/class-contact-form-endpoint.php

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,43 @@ public function register_routes() {
255255
'callback' => array( $this, 'get_forms_config' ),
256256
)
257257
);
258+
259+
register_rest_route(
260+
$this->namespace,
261+
$this->rest_base . '/export',
262+
array(
263+
'methods' => \WP_REST_Server::CREATABLE,
264+
'permission_callback' => array( $this, 'export_permissions_check' ),
265+
'callback' => array( $this, 'export_responses' ),
266+
'args' => array(
267+
'selected' => array(
268+
'type' => 'array',
269+
'items' => array( 'type' => 'integer' ),
270+
'default' => array(),
271+
),
272+
'post' => array(
273+
'type' => 'string',
274+
'default' => 'all',
275+
),
276+
'search' => array(
277+
'type' => 'string',
278+
'default' => '',
279+
),
280+
'status' => array(
281+
'type' => 'string',
282+
'default' => 'publish',
283+
),
284+
'before' => array(
285+
'type' => 'string',
286+
'default' => '',
287+
),
288+
'after' => array(
289+
'type' => 'string',
290+
'default' => '',
291+
),
292+
),
293+
)
294+
);
258295
}
259296

260297
/**
@@ -1070,4 +1107,106 @@ public function get_forms_config( WP_REST_Request $request ) { // phpcs:ignore V
10701107

10711108
return rest_ensure_response( $config );
10721109
}
1110+
1111+
/**
1112+
* Checks if a given request has permissions to export responses.
1113+
*
1114+
* @param WP_REST_Request $request The request object.
1115+
* @return bool|WP_Error True if the request can export, error object otherwise.
1116+
*/
1117+
public function export_permissions_check( $request ) { //phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1118+
if ( is_super_admin() ) {
1119+
return true;
1120+
}
1121+
1122+
if ( ! is_user_member_of_blog( get_current_user_id(), get_current_blog_id() ) ) {
1123+
return new WP_Error(
1124+
'rest_user_not_member',
1125+
__( 'Sorry, you are not a member of this site.', 'jetpack-forms' ),
1126+
array( 'status' => rest_authorization_required_code() )
1127+
);
1128+
}
1129+
1130+
if ( ! current_user_can( 'export' ) ) {
1131+
return new WP_Error(
1132+
'rest_user_cannot_export',
1133+
__( 'Sorry, you are not allowed to export form responses on this site.', 'jetpack-forms' ),
1134+
array( 'status' => rest_authorization_required_code() )
1135+
);
1136+
}
1137+
1138+
return true;
1139+
}
1140+
1141+
/**
1142+
* Export form responses to CSV.
1143+
*
1144+
* @param WP_REST_Request $request The request object.
1145+
* @return WP_REST_Response|WP_Error The response containing CSV data or error.
1146+
*/
1147+
public function export_responses( $request ) {
1148+
$selected = $request->get_param( 'selected' );
1149+
$post_id = $request->get_param( 'post' );
1150+
$search = $request->get_param( 'search' );
1151+
$status = $request->get_param( 'status' );
1152+
$before = $request->get_param( 'before' );
1153+
$after = $request->get_param( 'after' );
1154+
1155+
$query_args = array(
1156+
'post_type' => 'feedback',
1157+
'posts_per_page' => -1,
1158+
'post_status' => array( 'publish' ),
1159+
'order' => 'ASC',
1160+
'suppress_filters' => false,
1161+
);
1162+
1163+
if ( $status && $status !== 'publish' ) {
1164+
$query_args['post_status'] = explode( ',', $status );
1165+
}
1166+
1167+
if ( $post_id && $post_id !== 'all' ) {
1168+
$query_args['post_parent'] = intval( $post_id );
1169+
}
1170+
1171+
if ( $search ) {
1172+
$query_args['s'] = $search;
1173+
}
1174+
1175+
if ( ! empty( $selected ) ) {
1176+
$query_args['post__in'] = array_map( 'intval', $selected );
1177+
}
1178+
1179+
if ( $before || $after ) {
1180+
$date_query = array();
1181+
if ( $before ) {
1182+
$date_query['before'] = $before;
1183+
}
1184+
if ( $after ) {
1185+
$date_query['after'] = $after;
1186+
}
1187+
$query_args['date_query'] = array( $date_query );
1188+
}
1189+
1190+
$feedback_posts = get_posts( $query_args );
1191+
1192+
if ( empty( $feedback_posts ) ) {
1193+
return new WP_Error( 'no_responses', __( 'No responses found', 'jetpack-forms' ), array( 'status' => 404 ) );
1194+
}
1195+
1196+
$feedback_ids = array_map(
1197+
function ( $post ) {
1198+
return $post->ID;
1199+
},
1200+
$feedback_posts
1201+
);
1202+
1203+
$plugin = Contact_Form_Plugin::init();
1204+
$export_data = $plugin->get_export_feedback_data( $feedback_ids );
1205+
1206+
if ( empty( $export_data ) ) {
1207+
return new WP_Error( 'no_responses', __( 'No responses found', 'jetpack-forms' ), array( 'status' => 404 ) );
1208+
}
1209+
1210+
$plugin->download_feedback_as_csv( $export_data, $post_id );
1211+
}
10731212
}

projects/packages/forms/src/contact-form/class-contact-form-plugin.php

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,7 @@ protected function __construct() {
211211
add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'register_personal_data_exporter' ) );
212212
add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_personal_data_eraser' ) );
213213

214-
// Export to CSV feature
215214
if ( is_admin() ) {
216-
add_action( 'wp_ajax_feedback_export', array( $this, 'download_feedback_as_csv' ) );
217215
add_action( 'wp_ajax_create_new_form', array( $this, 'create_new_form' ) );
218216
}
219217
add_action( 'admin_menu', array( $this, 'admin_menu' ) );
@@ -2758,21 +2756,20 @@ function ( $selected ) {
27582756

27592757
/**
27602758
* Download exported data as CSV
2759+
*
2760+
* @param array $data Export data to generate CSV from.
2761+
* @param string $post_id Optional. Post ID for filename generation.
27612762
*/
2762-
public function download_feedback_as_csv() {
2763-
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- verification is done on get_feedback_entries_from_post function
2764-
$post_data = wp_unslash( $_POST );
2765-
$data = $this->get_feedback_entries_from_post();
2766-
2763+
public function download_feedback_as_csv( $data, $post_id = '' ) {
27672764
if ( empty( $data ) ) {
27682765
return;
27692766
}
27702767

27712768
// Check if we want to download all the feedbacks or just a certain contact form
2772-
if ( ! empty( $post_data['post'] ) && $post_data['post'] !== 'all' ) {
2769+
if ( ! empty( $post_id ) && $post_id !== 'all' ) {
27732770
$filename = sprintf(
27742771
'%s - %s.csv',
2775-
Util::get_export_filename( get_the_title( (int) $post_data['post'] ) ),
2772+
Util::get_export_filename( get_the_title( (int) $post_id ) ),
27762773
gmdate( 'Y-m-d H:i' )
27772774
);
27782775
} else {

projects/packages/forms/src/dashboard/hooks/use-export-responses.ts

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,32 @@
33
*/
44
import jetpackAnalytics from '@automattic/jetpack-analytics';
55
import { useBreakpointMatch } from '@automattic/jetpack-components';
6+
import apiFetch from '@wordpress/api-fetch';
67
import { store as coreStore } from '@wordpress/core-data';
78
import { useSelect } from '@wordpress/data';
89
import { useState, useCallback, useEffect } from '@wordpress/element';
910
import { __ } from '@wordpress/i18n';
1011
/**
1112
* Internal dependencies
1213
*/
13-
import { config } from '..';
1414
import { store as dashboardStore } from '../store';
1515

16+
type ExportData = {
17+
selected: number[];
18+
post: string;
19+
search: string;
20+
status: string;
21+
before?: string;
22+
after?: string;
23+
};
24+
1625
type ExportHookReturn = {
1726
showExportModal: boolean;
1827
openModal: () => void;
1928
closeModal: () => void;
2029
autoConnectGdrive: boolean;
2130
userCanExport: boolean;
22-
onExport: ( action: string, nonceName: string ) => Promise< Response >;
31+
onExport: () => Promise< Response >;
2332
selectedResponsesCount: number;
2433
currentStatus: string;
2534
exportLabel: string;
@@ -75,25 +84,26 @@ export default function useExportResponses(): ExportHookReturn {
7584
return { selected: getSelectedResponsesFromCurrentDataset(), currentQuery: getCurrentQuery() };
7685
}, [] );
7786

78-
const onExport = useCallback(
79-
( action: string, nonceName: string ) => {
80-
const data = new FormData();
81-
data.append( 'action', action );
82-
data.append( nonceName, config( 'exportNonce' ) );
83-
selected.forEach( ( id: string ) => data.append( 'selected[]', id ) );
84-
data.append( 'post', currentQuery.parent || 'all' );
85-
data.append( 'search', currentQuery.search || '' );
86-
data.append( 'status', currentQuery.status );
87-
88-
if ( currentQuery.before && currentQuery.after ) {
89-
data.append( 'before', currentQuery.before );
90-
data.append( 'after', currentQuery.after );
91-
}
92-
93-
return fetch( window.ajaxurl, { method: 'POST', body: data } );
94-
},
95-
[ currentQuery, selected ]
96-
);
87+
const onExport = useCallback( () => {
88+
const exportData: ExportData = {
89+
selected: selected.map( id => parseInt( id, 10 ) ),
90+
post: currentQuery.parent ? String( currentQuery.parent ) : 'all',
91+
search: currentQuery.search || '',
92+
status: currentQuery.status || 'publish',
93+
};
94+
95+
if ( currentQuery.before && currentQuery.after ) {
96+
exportData.before = currentQuery.before;
97+
exportData.after = currentQuery.after;
98+
}
99+
100+
return apiFetch( {
101+
path: '/wp/v2/feedback/export',
102+
method: 'POST',
103+
data: exportData,
104+
parse: false,
105+
} );
106+
}, [ currentQuery, selected ] );
97107

98108
useEffect( () => {
99109
const url = new URL( window.location.href );

0 commit comments

Comments
 (0)