'pubforms_form', 'page' => 'pubforms-entries' ), $args ); if ( $form_id ) { $args['form_id'] = $form_id; } return add_query_arg( $args, admin_url( 'edit.php' ) ); } function pubforms_entry_url( $form_id, $entry_id ) { return pubforms_entries_url( $form_id, array( 'entry' => $entry_id ) ); } /** * Link that serves a stored file (upload or signature) to a signed-in user who can edit the form. * Files are private on disk; this is the only way to reach them. */ function pubforms_file_url( $entry_id, $field_id ) { $url = add_query_arg( array( 'action' => 'pubforms_download', 'entry' => (int) $entry_id, 'field' => (string) $field_id, ), admin_url( 'admin-post.php' ) ); return wp_nonce_url( $url, 'pubforms_download_' . (int) $entry_id ); } /** * Work out which file an entry's field points at and whether this user may have it. * * @return array|WP_Error path, name, type, inline (bool). */ function pubforms_resolve_file( $entry_id, $field_id ) { $entry = pubforms_get_entry( $entry_id ); if ( ! $entry || ! pubforms_request_form( $entry->form_id ) ) { return new WP_Error( 'forbidden', __( 'You do not have permission to view this file.', 'pubforms' ) ); } foreach ( $entry->items as $item ) { if ( ( $item['id'] ?? '' ) !== $field_id || ! in_array( $item['type'] ?? '', array( 'file', 'signature' ), true ) ) { continue; } // Rebuild the path from the file name inside the form's folder, so only files PubForms stored are reachable. $stored = wp_basename( (string) ( $item['filename'] ?? '' ) ); $dir = pubforms_uploads_dir( $entry->form_id ); $path = $dir['path'] . '/' . $stored; if ( '' === $stored || ! is_file( $path ) ) { break; } $is_sig = 'signature' === $item['type']; $name = $is_sig ? 'signature.png' : sanitize_file_name( (string) ( $item['name'] ?? '' ) ); $name = '' !== $name ? $name : $stored; $type = $is_sig ? 'image/png' : (string) wp_check_filetype( $stored )['type']; return array( 'path' => $path, 'name' => $name, 'type' => '' !== $type ? $type : 'application/octet-stream', 'inline' => $is_sig, ); } return new WP_Error( 'missing', __( 'That file could not be found.', 'pubforms' ) ); } add_action( 'admin_post_pubforms_download', function () { $entry_id = absint( $_GET['entry'] ?? 0 ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- verified by check_admin_referer() on the next line. check_admin_referer( 'pubforms_download_' . $entry_id ); $field = sanitize_key( wp_unslash( $_GET['field'] ?? '' ) ); $file = pubforms_resolve_file( $entry_id, $field ); if ( is_wp_error( $file ) ) { wp_die( esc_html( $file->get_error_message() ), '', array( 'response' => 'forbidden' === $file->get_error_code() ? 403 : 404 ) ); } nocache_headers(); header( 'Content-Type: ' . $file['type'] ); header( 'Content-Disposition: ' . ( $file['inline'] ? 'inline' : 'attachment' ) . '; filename="' . str_replace( '"', '', $file['name'] ) . '"' ); header( 'Content-Length: ' . filesize( $file['path'] ) ); header( 'X-Content-Type-Options: nosniff' ); header( "Content-Security-Policy: default-src 'none'; sandbox" ); readfile( $file['path'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- streams a private file after the capability and nonce checks above. exit; } ); add_action( 'admin_menu', function () { $hook = add_submenu_page( 'edit.php?post_type=pubforms_form', __( 'Entries', 'pubforms' ), __( 'Entries', 'pubforms' ), 'edit_posts', 'pubforms-entries', 'pubforms_render_entries_page' ); add_action( "load-$hook", 'pubforms_entries_page_load' ); } ); function pubforms_notice_messages() { return array( /* translators: %d: number of entries */ 'deleted' => __( 'Deleted %d entries.', 'pubforms' ), /* translators: %d: number of entries */ 'marked' => __( 'Updated %d entries.', 'pubforms' ), 'none' => __( 'Select at least one entry first.', 'pubforms' ), ); } /** * The form an admin request is about, only if the current user may edit it. */ function pubforms_request_form( $form_id ) { $form = $form_id ? pubforms_get_form( $form_id ) : null; return ( $form && current_user_can( 'edit_post', $form['id'] ) ) ? $form : null; } /** * Runs before the page prints: help text and bulk actions (so we can redirect afterwards). */ function pubforms_entries_page_load() { $screen = get_current_screen(); if ( $screen ) { $screen->add_help_tab( array( 'id' => 'pubforms-entries-help', 'title' => __( 'Entries', 'pubforms' ), 'content' => '

' . esc_html__( 'Every submission is kept here, on your own site. Click a date to read an entry. Use the checkboxes and the Bulk actions menu to delete entries or mark them read or unread. Export CSV downloads all of a form\'s entries. No visitor IP address or browser details are stored.', 'pubforms' ) . '

', ) ); } // phpcs:disable WordPress.Security.NonceVerification.Recommended -- verified below with check_admin_referer. $action = sanitize_key( $_REQUEST['action'] ?? '' ); if ( '' === $action || '-1' === $action ) { $action = sanitize_key( $_REQUEST['action2'] ?? '' ); } if ( ! in_array( $action, array( 'delete', 'mark_read', 'mark_unread' ), true ) || empty( $_REQUEST['form_id'] ) ) { return; } $form_id = absint( $_REQUEST['form_id'] ); $ids = array_map( 'absint', (array) ( $_REQUEST['entry'] ?? array() ) ); // phpcs:enable check_admin_referer( 'bulk-entries' ); if ( ! pubforms_request_form( $form_id ) ) { wp_die( esc_html__( 'You do not have permission to manage these entries.', 'pubforms' ), '', array( 'response' => 403 ) ); } if ( ! $ids ) { wp_safe_redirect( pubforms_entries_url( $form_id, array( 'pubforms_notice' => 'none' ) ) ); exit; } $done = pubforms_bulk_entry_action( $form_id, $action, $ids ); wp_safe_redirect( pubforms_entries_url( $form_id, array( 'pubforms_notice' => 'delete' === $action ? 'deleted' : 'marked', 'n' => $done ) ) ); exit; } /** * Apply a bulk action to entries of one form. Entries of other forms are ignored. * * @return int Entries changed. */ function pubforms_bulk_entry_action( $form_id, $action, $entry_ids ) { $done = 0; foreach ( $entry_ids as $id ) { $entry = pubforms_get_entry( $id ); if ( ! $entry || (int) $entry->form_id !== (int) $form_id ) { continue; } if ( 'delete' === $action ) { $ok = pubforms_delete_entry( $id ); } else { $ok = pubforms_set_entry_status( $id, 'mark_read' === $action ? 'read' : 'unread' ); } if ( $ok ) { $done++; } } return $done; } function pubforms_render_entries_page() { if ( ! current_user_can( 'edit_posts' ) ) { wp_die( esc_html__( 'You do not have permission to view this page.', 'pubforms' ) ); } echo '

' . esc_html__( 'Entries', 'pubforms' ) . '

'; // phpcs:disable WordPress.Security.NonceVerification.Recommended -- display only. if ( isset( $_GET['pubforms_notice'] ) ) { $code = sanitize_key( $_GET['pubforms_notice'] ); $messages = pubforms_notice_messages(); if ( isset( $messages[ $code ] ) ) { echo '

' . esc_html( sprintf( $messages[ $code ], absint( $_GET['n'] ?? 0 ) ) ) . '

'; } } $form_id = absint( $_GET['form_id'] ?? 0 ); $entry_id = absint( $_GET['entry'] ?? 0 ); // phpcs:enable $form = pubforms_request_form( $form_id ); if ( $form && $entry_id ) { pubforms_render_entry( $form, $entry_id ); } elseif ( $form ) { pubforms_render_entry_list( $form ); } else { pubforms_render_forms_overview(); } echo '
'; } function pubforms_render_forms_overview() { // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page -- a bounded list for an admin overview. $forms = array_filter( get_posts( array( 'post_type' => 'pubforms_form', 'post_status' => array( 'publish', 'draft', 'pending', 'private' ), 'posts_per_page' => 200 ) ), function ( $f ) { return current_user_can( 'edit_post', $f->ID ); } ); if ( ! $forms ) { echo '

' . esc_html__( 'No forms yet.', 'pubforms' ) . ' ' . esc_html__( 'Create one', 'pubforms' ) . '

'; return; } echo ''; foreach ( $forms as $form ) { $c = pubforms_entry_counts( $form->ID ); echo ''; } echo '
' . esc_html__( 'Form', 'pubforms' ) . '' . esc_html__( 'Entries', 'pubforms' ) . '' . esc_html__( 'Unread', 'pubforms' ) . '' . esc_html__( 'Abandoned', 'pubforms' ) . '
' . esc_html( get_the_title( $form ) ) . '' . (int) $c['total'] . '' . (int) $c['unread'] . '' . (int) $c['partial'] . '
'; } /** * Aggregate results (survey/poll/quiz) for a form, tallied from its entries. */ function pubforms_render_results_box( $form ) { $out = ''; foreach ( $form['fields'] as $field ) { if ( ! in_array( $field['type'], array( 'radio', 'checkbox_multi', 'quiz' ), true ) || empty( $field['options'] ) ) { continue; } $tally = pubforms_field_tally( $form, $field['id'] ); if ( ! $tally ) { continue; } $total = (int) array_sum( $tally ); $out .= '

' . esc_html( $field['label'] ) . '

'; foreach ( $field['options'] as $option ) { $count = (int) ( $tally[ $option ] ?? 0 ); if ( ! $count ) { continue; } /* translators: 1: value, 2: count, 3: percentage */ $out .= ''; } $out .= '
' . esc_html( $option ) . '' . esc_html( sprintf( __( '%1$d responses (%2$d%%)', 'pubforms' ), $count, (int) round( $count / max( 1, $total ) * 100 ) ) ) . '
'; } if ( '' === $out ) { return ''; } return '

' . esc_html__( 'Results', 'pubforms' ) . '

' . $out . '
'; } function pubforms_render_entry_list( $form ) { require_once PUBFORMS_DIR . 'includes/class-pubforms-entries-list-table.php'; // phpcs:disable WordPress.Security.NonceVerification.Recommended -- display only. $status = sanitize_key( wp_unslash( $_GET['status'] ?? '' ) ); $date_from = sanitize_text_field( wp_unslash( $_GET['date_from'] ?? '' ) ); $date_to = sanitize_text_field( wp_unslash( $_GET['date_to'] ?? '' ) ); // phpcs:enable $export_args = array( 'action' => 'pubforms_export', 'form_id' => $form['id'] ); if ( $status ) { $export_args['status'] = $status; } if ( $date_from ) { $export_args['date_from'] = $date_from; } if ( $date_to ) { $export_args['date_to'] = $date_to; } $export = wp_nonce_url( add_query_arg( $export_args, admin_url( 'admin-post.php' ) ), 'pubforms_export_' . $form['id'] ); ?>

←

prepare_items(); ?>

views(); $table->search_box( __( 'Search entries', 'pubforms' ), 'pubforms-entry' ); $table->display(); ?>
form_id !== (int) $form['id'] ) { echo '

' . esc_html__( 'Entry not found.', 'pubforms' ) . ' ' . esc_html__( 'Back to entries', 'pubforms' ) . '

'; return; } if ( 'unread' === $entry->status ) { pubforms_set_entry_status( $entry->id, 'read' ); } $delete = wp_nonce_url( add_query_arg( array( 'action' => 'pubforms_delete_entry', 'form_id' => $form['id'], 'entry' => $entry->id ), admin_url( 'admin-post.php' ) ), 'pubforms_delete_' . $entry->id ); ?>

←

created_at, get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ) ) ); ?> notified ? '' : ' · ' . esc_html__( 'The notification email could not be sent', 'pubforms' ); ?>

items as $item ) : ?>
<?php esc_attr_e( 'Signature', 'pubforms' ); ?> ✓ ✕


id, 'pubforms_note_nonce' ); ?>
403 ) ); } pubforms_bulk_entry_action( $form_id, 'delete', array( $entry_id ) ); wp_safe_redirect( pubforms_entries_url( $form_id, array( 'pubforms_notice' => 'deleted', 'n' => 1 ) ) ); exit; } ); add_action( 'admin_post_pubforms_export', function () { $form_id = absint( $_GET['form_id'] ?? 0 ); // phpcs:ignore WordPress.Security.NonceVerification check_admin_referer( 'pubforms_export_' . $form_id ); $form = pubforms_request_form( $form_id ); if ( ! $form ) { wp_die( esc_html__( 'You do not have permission to export these entries.', 'pubforms' ), '', array( 'response' => 403 ) ); } nocache_headers(); header( 'Content-Type: text/csv; charset=utf-8' ); header( 'Content-Disposition: attachment; filename="' . sanitize_file_name( sanitize_title( $form['title'] ) . '-entries.csv' ) . '"' ); $filters = array( 'status' => sanitize_key( wp_unslash( $_GET['status'] ?? '' ) ), 'date_from' => sanitize_text_field( wp_unslash( $_GET['date_from'] ?? '' ) ), 'date_to' => sanitize_text_field( wp_unslash( $_GET['date_to'] ?? '' ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $out = fopen( 'php://output', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- streaming the CSV download. foreach ( pubforms_entries_csv_rows( $form, $filters ) as $row ) { fputcsv( $out, $row, ',', '"', '' ); } fclose( $out ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- php://output stream. exit; } ); add_action( 'admin_post_pubforms_save_entry_note', function () { $form_id = absint( $_POST['form_id'] ?? 0 ); // phpcs:ignore WordPress.Security.NonceVerification $entry_id = absint( $_POST['entry'] ?? 0 ); // phpcs:ignore WordPress.Security.NonceVerification check_admin_referer( 'pubforms_entry_note_' . $entry_id, 'pubforms_note_nonce' ); if ( ! pubforms_request_form( $form_id ) ) { wp_die( esc_html__( 'You do not have permission to manage these entries.', 'pubforms' ), '', array( 'response' => 403 ) ); } $entry = pubforms_get_entry( $entry_id ); if ( ! $entry || (int) $entry->form_id !== (int) $form_id ) { wp_die( esc_html__( 'Entry not found.', 'pubforms' ), '', array( 'response' => 404 ) ); } $note = isset( $_POST['pubforms_note'] ) ? wp_unslash( $_POST['pubforms_note'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- sanitised in pubforms_set_entry_note(). pubforms_set_entry_note( $entry_id, $note ); wp_safe_redirect( pubforms_entry_url( $form_id, $entry_id ) ); exit; } );