FileMaster
Search
Toggle Dark Mode
Home
/
.
/
wp-content
/
plugins
/
sureforms
/
inc
Edit File: entries.php
<?php /** * Sureforms entries. * * @package sureforms. * @since 2.0.0 */ namespace SRFM\Inc; use SRFM\Inc\Database\Tables\Entries as EntriesTable; use SRFM\Inc\Traits\Get_Instance; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Entries Class. * * @since 2.0.0 */ class Entries { use Get_Instance; /** * Constructor * * @since 2.0.0 */ public function __construct() { // Constructor code here. } /** * Get entries with filters, sorting, and pagination. * * @param array<string,mixed> $args { * Optional. An array of arguments to customize the query. * * @type int $form_id Form ID to filter entries. Default 0 (all forms). * @type string $status Entry status: 'all', 'read', 'unread', 'trash'. Default 'all'. * @type string $search Search term to filter entries by entry ID. Default empty. * @type string $date_from Start date for filtering entries (YYYY-MM-DD format). Default empty. * @type string $date_to End date for filtering entries (YYYY-MM-DD format). Default empty. * @type string $orderby Column to order by. Default 'created_at'. * @type string $order Sort direction: 'ASC' or 'DESC'. Default 'DESC'. * @type int $per_page Number of entries per page. Default 20. * @type int $page Current page number. Default 1. * @type array<mixed> $entry_ids Specific entry IDs to fetch. Default empty array. * } * * @since 2.0.0 * @return array<string,mixed> { * @type array<mixed> $entries Array of entry objects. * @type int $total Total number of entries matching the query. * @type int $per_page Number of entries per page. * @type int $current_page Current page number. * @type int $total_pages Total number of pages. * @type bool $emptyTrash Whether the trash is empty (true) or contains items (false). * } */ public static function get_entries( $args = [] ) { $defaults = [ 'form_id' => 0, 'status' => 'all', 'search' => '', 'date_from' => '', 'date_to' => '', 'orderby' => 'created_at', 'order' => 'DESC', 'per_page' => 20, 'page' => 1, 'entry_ids' => [], ]; /** * Parsed and sanitized arguments for the entries query. * * @var array{form_id: int, status: string, search: string, date_from: string, date_to: string, orderby: string, order: string, per_page: int, page: int, entry_ids: array<int>} $args */ $args = wp_parse_args( $args, $defaults ); // Build where conditions. $where_conditions = self::build_where_conditions( $args ); // Get total count for pagination. $total = EntriesTable::get_instance()->get_total_count( $where_conditions ); // Calculate offset. $offset = ( absint( $args['page'] ) - 1 ) * absint( $args['per_page'] ); // Get entries. $entries = EntriesTable::get_all( [ 'where' => $where_conditions, 'columns' => '*', 'orderby' => Helper::get_string_value( $args['orderby'] ), 'order' => Helper::get_string_value( $args['order'] ), 'limit' => absint( $args['per_page'] ), 'offset' => $offset, ] ); // Check if trash is empty. $trash_count = EntriesTable::get_instance()->get_total_count( [ [ [ 'key' => 'status', 'compare' => '=', 'value' => 'trash', ], ], ] ); return [ 'entries' => $entries, 'total' => $total, 'per_page' => absint( $args['per_page'] ), 'current_page' => absint( $args['page'] ), 'total_pages' => ceil( $total / absint( $args['per_page'] ) ), 'emptyTrash' => 0 === $trash_count, ]; } /** * Update entry status (trash/untrash/read/unread). * * @param int|array<int> $entry_ids Entry ID or array of entry IDs. * @param string $status New status: 'trash', 'unread', 'read', or 'restore'. * * @since 2.0.0 * @return array<string,mixed> { * @type bool $success Whether the operation was successful. * @type int $updated Number of entries updated. * @type array<string> $errors Array of error messages. * } */ public static function update_status( $entry_ids, $status ) { $entry_ids = is_array( $entry_ids ) ? array_map( 'absint', $entry_ids ) : [ absint( $entry_ids ) ]; $entry_ids = array_filter( $entry_ids ); // Remove any zero values. if ( empty( $entry_ids ) ) { return [ 'success' => false, 'updated' => 0, 'errors' => [ __( 'No valid entry IDs provided.', 'sureforms' ) ], ]; } // Validate status. $valid_statuses = [ 'trash', 'unread', 'read', 'restore' ]; if ( ! in_array( $status, $valid_statuses, true ) ) { return [ 'success' => false, 'updated' => 0, 'errors' => [ __( 'Invalid status provided.', 'sureforms' ) ], ]; } // Map 'restore' to 'unread'. $actual_status = 'restore' === $status ? 'unread' : $status; $updated = 0; $errors = []; foreach ( $entry_ids as $entry_id ) { $result = EntriesTable::update( $entry_id, [ 'status' => $actual_status ] ); if ( false === $result ) { $errors[] = sprintf( // translators: %d is the entry ID. __( 'Failed to update entry #%d.', 'sureforms' ), $entry_id ); } else { ++$updated; } } return [ 'success' => $updated > 0, 'updated' => $updated, 'errors' => $errors, ]; } /** * Permanently delete entries. * * @param int|array<int> $entry_ids Entry ID or array of entry IDs. * * @since 2.0.0 * @return array<string,mixed> { * @type bool $success Whether the operation was successful. * @type int $deleted Number of entries deleted. * @type array<string> $errors Array of error messages. * } */ public static function delete_entries( $entry_ids ) { $entry_ids = is_array( $entry_ids ) ? array_map( 'absint', $entry_ids ) : [ absint( $entry_ids ) ]; $entry_ids = array_filter( $entry_ids ); if ( empty( $entry_ids ) ) { return [ 'success' => false, 'deleted' => 0, 'errors' => [ __( 'No valid entry IDs provided.', 'sureforms' ) ], ]; } $deleted = 0; $errors = []; foreach ( $entry_ids as $entry_id ) { $result = EntriesTable::delete( $entry_id ); if ( false === $result ) { $errors[] = sprintf( // translators: %d is the entry ID. __( 'Failed to delete entry #%d.', 'sureforms' ), $entry_id ); } else { ++$deleted; } } return [ 'success' => $deleted > 0, 'deleted' => $deleted, 'errors' => $errors, ]; } /** * Export entries to CSV format. * * @param array<string,mixed> $args { * Optional. An array of arguments for export. * * @type int|array<int> $entry_ids Entry ID or array of entry IDs to export. * @type int $form_id Form ID to export all entries from. * @type string $status Entry status filter. * @type string $search Search term filter. * @type string $date_from Start date for filtering entries (YYYY-MM-DD format). * @type string $date_to End date for filtering entries (YYYY-MM-DD format). * } * * @since 2.0.0 * @return array<string,mixed> { * @type bool $success Whether the export was successful. * @type string $filename Export filename (if single form). * @type string $filepath Full path to the exported file. * @type string $type Export type: 'csv' or 'zip'. * @type string $error Error message if failed. * } */ public static function export_entries( $args = [] ) { $defaults = [ 'entry_ids' => [], 'form_id' => 0, 'status' => 'all', 'search' => '', 'date_from' => '', 'date_to' => '', ]; /** * Parsed and sanitized arguments for the export operation. * * @var array{entry_ids: int|array<int>, form_id: int, status: string, search: string, date_from: string, date_to: string} $args */ $args = wp_parse_args( $args, $defaults ); // Get entry IDs to export. if ( empty( $args['entry_ids'] ) ) { // If no specific entry IDs provided, get all matching entries. $where_conditions = self::build_where_conditions( $args ); $all_entries = EntriesTable::get_all( [ 'where' => $where_conditions, 'columns' => 'ID', ], false ); $entry_ids = array_map( 'absint', array_column( $all_entries, 'ID' ) ); } else { /** * Entry IDs converted to array of integers. * * @var array<int> $entry_ids */ $entry_ids = is_array( $args['entry_ids'] ) ? array_map( 'absint', $args['entry_ids'] ) : [ absint( (int) $args['entry_ids'] ) ]; } if ( empty( $entry_ids ) ) { return [ 'success' => false, 'error' => __( 'No entries found to export.', 'sureforms' ), ]; } // Get form IDs from entry IDs. $form_ids = EntriesTable::get_form_ids_by_entries( $entry_ids ); $is_single_form = count( $form_ids ) === 1; $temp_dir = wp_normalize_path( trailingslashit( get_temp_dir() ) ); // Check if temp directory is writable. if ( ! wp_is_writable( $temp_dir ) ) { return [ 'success' => false, 'error' => __( 'Temporary directory is not writable.', 'sureforms' ), ]; } $csv_files = []; $zip = null; $temp_zip = ''; // Create ZIP if multiple forms. if ( ! $is_single_form ) { if ( ! class_exists( 'ZipArchive' ) ) { return [ 'success' => false, 'error' => __( 'ZipArchive class is not available.', 'sureforms' ), ]; } $temp_zip = $temp_dir . 'srfm-entries-export-' . time() . '.zip'; $zip = new \ZipArchive(); if ( ! $zip->open( $temp_zip, \ZipArchive::CREATE ) ) { return [ 'success' => false, 'error' => __( 'Unable to create ZIP file.', 'sureforms' ), ]; } } $csv_filepath = ''; // Process each form. foreach ( $form_ids as $form_id ) { $results = self::get_entries_data_for_export( $entry_ids, $form_id ); if ( empty( $results ) ) { continue; } $sanitized_form_title = sanitize_title( get_the_title( $form_id ) ); $sanitized_form_title = ! empty( $sanitized_form_title ) ? $sanitized_form_title : "srfm-entries-{$form_id}"; $csv_filename = 'srfm-entries-' . $sanitized_form_title . '.csv'; $csv_filepath = $temp_dir . $csv_filename; if ( file_exists( $csv_filepath ) ) { unlink( $csv_filepath ); } $stream = fopen( $csv_filepath, 'wb' ); // phpcs:ignore -- Using fopen to decrease the memory use. if ( ! is_resource( $stream ) ) { continue; } $csv_files[] = $csv_filepath; // Build CSV content. $block_data = self::build_block_key_map_and_labels( $results ); self::write_csv_header( $stream, $block_data['labels'] ); self::write_csv_rows( $stream, $results, $block_data['map'] ); fclose( $stream ); // phpcs:ignore -- Using fopen to decrease the memory use. // Add to ZIP if multiple forms. if ( ! $is_single_form && $zip && filesize( $csv_filepath ) > 0 ) { $zip->addFile( $csv_filepath, $csv_filename ); } } // Single form - return CSV. if ( $is_single_form && ! empty( $csv_filepath ) && file_exists( $csv_filepath ) ) { return [ 'success' => true, 'filename' => basename( $csv_filepath ), 'filepath' => $csv_filepath, 'type' => 'csv', ]; } // Multiple forms - return ZIP. if ( ! $is_single_form && $zip ) { $zip->close(); // Clean up CSV files. foreach ( $csv_files as $csv_file ) { if ( file_exists( $csv_file ) ) { unlink( $csv_file ); } } return [ 'success' => true, 'filename' => 'SureForms-Entries.zip', 'filepath' => $temp_zip, 'type' => 'zip', ]; } return [ 'success' => false, 'error' => __( 'Unable to generate export file.', 'sureforms' ), ]; } /** * Get adjacent entry IDs (previous and next) for navigation. * Navigation follows chronological order: Previous = older, Next = newer. * * @param int $current_entry_id Current entry ID. * @param array<string,mixed> $args { * Optional. An array of arguments to filter the navigation context. * * @type int $form_id Form ID to filter entries. Default 0 (all forms). * @type string $status Entry status: 'all', 'read', 'unread', 'trash'. Default 'all'. * @type string $search Search term to filter entries by entry ID. Default empty. * @type string $date_from Start date for filtering entries (YYYY-MM-DD format). Default empty. * @type string $date_to End date for filtering entries (YYYY-MM-DD format). Default empty. * @type string $orderby Column to order by. Default 'created_at'. * @type string $order Sort direction: 'ASC' or 'DESC'. Default 'DESC'. * } * * @since 2.4.0 * @return array<string,int|null> { * @type int|null $previous_id Previous entry ID (older entry) or null if at the oldest. * @type int|null $next_id Next entry ID (newer entry) or null if at the newest. * } */ public static function get_adjacent_entry_ids( $current_entry_id, $args = [] ) { $defaults = [ 'form_id' => 0, 'status' => 'all', 'search' => '', 'date_from' => '', 'date_to' => '', 'orderby' => 'created_at', 'order' => 'DESC', ]; $args = wp_parse_args( $args, $defaults ); // Build where conditions. $where_conditions = self::build_where_conditions( $args ); // Get all entry IDs in chronological order (oldest to newest). // This ensures Previous = older, Next = newer regardless of listing page sort. $all_entries = EntriesTable::get_all( [ 'where' => $where_conditions, 'columns' => 'ID', 'orderby' => 'created_at', 'order' => 'ASC', ], false ); // Extract entry IDs into a simple array. $entry_ids = array_map( static function ( $entry ) { return is_array( $entry ) ? Helper::get_integer_value( $entry['ID'] ) : 0; }, $all_entries ); // Find the position of the current entry. $current_position = array_search( absint( $current_entry_id ), $entry_ids, true ); if ( false === $current_position ) { // Current entry not found in the filtered list. return [ 'previous_id' => null, 'next_id' => null, ]; } // Convert to integer after validation. $current_position = Helper::get_integer_value( $current_position ); // Get previous and next entry IDs. $previous_id = $current_position > 0 ? $entry_ids[ $current_position - 1 ] : null; $next_id = $current_position < count( $entry_ids ) - 1 ? $entry_ids[ $current_position + 1 ] : null; return [ 'previous_id' => $previous_id, 'next_id' => $next_id, ]; } /** * Build where conditions for entry queries. * * @param array<string, int|string|array<int>> $args Query arguments. * * @since 2.0.0 * @return array<mixed> Where conditions array. */ private static function build_where_conditions( $args ) { $where_conditions = []; // Filter by entry IDs. if ( ! empty( $args['entry_ids'] ) && is_array( $args['entry_ids'] ) ) { $where_conditions[] = [ [ 'key' => 'ID', 'compare' => 'IN', 'value' => array_map( 'absint', $args['entry_ids'] ), ], ]; return $where_conditions; } // Filter by status. if ( 'all' !== $args['status'] ) { $where_conditions[] = [ [ 'key' => 'status', 'compare' => '=', 'value' => Helper::get_string_value( $args['status'] ), ], ]; } else { // Exclude trash when status is 'all'. $where_conditions[] = [ [ 'key' => 'status', 'compare' => '!=', 'value' => 'trash', ], ]; } // Filter by form ID. $form_id = Helper::get_integer_value( $args['form_id'] ?? 0 ); if ( ! empty( $args['form_id'] ) && $form_id > 0 ) { $where_conditions[] = [ [ 'key' => 'form_id', 'compare' => '=', 'value' => $form_id, ], ]; } // Filter by date range. if ( ! empty( $args['date_from'] ) || ! empty( $args['date_to'] ) ) { $date_conditions = []; if ( ! empty( $args['date_from'] ) ) { $date_conditions[] = [ 'key' => 'created_at', 'compare' => '>=', 'value' => Helper::get_string_value( $args['date_from'] ), ]; } if ( ! empty( $args['date_to'] ) ) { $date_conditions[] = [ 'key' => 'created_at', 'compare' => '<=', 'value' => Helper::get_string_value( $args['date_to'] ), ]; } if ( ! empty( $date_conditions ) ) { $where_conditions[] = $date_conditions; } } // Filter by search (entry ID + form title). if ( ! empty( $args['search'] ) && is_string( $args['search'] ) ) { $search_term = sanitize_text_field( $args['search'] ); $search_group = [ 'RELATION' => 'OR' ]; // If numeric, match entry ID. if ( is_numeric( $search_term ) ) { $search_group[] = [ 'key' => 'ID', 'compare' => '=', 'value' => absint( $search_term ), ]; } // Match form titles. $matching_form_ids = self::get_form_ids_by_title( $search_term ); if ( ! empty( $matching_form_ids ) ) { $search_group[] = [ 'key' => 'form_id', 'compare' => 'IN', 'value' => $matching_form_ids, ]; } // Only add if we have search conditions, otherwise force empty result. if ( count( $search_group ) > 1 ) { $where_conditions[] = $search_group; } else { $where_conditions[] = [ [ 'key' => 'ID', 'compare' => '=', 'value' => 0, ], ]; } } return $where_conditions; } /** * Get form IDs whose title matches the search term. * * @param string $search_term Search term to match against form titles. * * @since 2.6.0 * @return array<int> Array of matching form IDs. */ private static function get_form_ids_by_title( $search_term ) { $forms = get_posts( [ 'post_type' => SRFM_FORMS_POST_TYPE, 'post_status' => 'any', 's' => $search_term, 'search_columns' => [ 'post_title' ], 'posts_per_page' => 100, 'fields' => 'ids', 'no_found_rows' => true, ] ); return ! empty( $forms ) ? array_map( 'absint', $forms ) : []; } /** * Get entries data for export based on entry IDs and form ID. * * @param array<int> $entry_ids Entry IDs. * @param int $form_id Form ID. * * @since 2.0.0 * @return array<mixed> Entry data. */ private static function get_entries_data_for_export( $entry_ids, $form_id ) { return EntriesTable::get_all( [ 'where' => [ [ [ 'key' => 'ID', 'compare' => 'IN', 'value' => $entry_ids, ], [ 'key' => 'form_id', 'compare' => '=', 'value' => $form_id, ], ], ], 'columns' => '*', ], false ); } /** * Build block key map and labels for CSV export. * * @param array<mixed> $results Entry results. * * @since 2.0.0 * @return array{map: array<string,string>, labels: array<string,string>} Map and labels. */ private static function build_block_key_map_and_labels( $results ) { $block_key_map = []; $block_labels = []; $excluded = Helper::get_excluded_fields(); foreach ( $results as $entry ) { $form_data = is_array( $entry ) && isset( $entry['form_data'] ) ? Helper::get_array_value( $entry['form_data'] ) : []; foreach ( $form_data as $srfm_key => $value ) { if ( in_array( $srfm_key, $excluded, true ) ) { continue; } $block_id = Helper::get_block_id_from_key( $srfm_key ); if ( empty( $block_id ) ) { continue; } $block_key_map[ $block_id ] = $srfm_key; $block_labels[ $block_id ] = Helper::get_field_label_from_key( $srfm_key ); } } return [ 'map' => $block_key_map, 'labels' => $block_labels, ]; } /** * Write CSV header row. * * @param resource $stream File stream. * @param array<string> $block_labels Block labels. * * @since 2.0.0 * @return void */ private static function write_csv_header( $stream, $block_labels ) { $header = array_merge( [ __( 'Entry ID', 'sureforms' ), __( 'Date', 'sureforms' ), __( 'Status', 'sureforms' ) ], array_values( $block_labels ) ); fputcsv( $stream, $header ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fputcsv } /** * Write CSV data rows. * * @param resource $stream File stream. * @param array<mixed> $results Entry results. * @param array<string,string> $block_key_map Block key map. * * @since 2.0.0 * @return void */ private static function write_csv_rows( $stream, $results, $block_key_map ) { foreach ( $results as $entry ) { if ( ! is_array( $entry ) ) { continue; } $row = []; $row[] = isset( $entry['ID'] ) ? Helper::get_integer_value( $entry['ID'] ) : ''; $row[] = isset( $entry['created_at'] ) ? Helper::get_string_value( $entry['created_at'] ) : ''; $row[] = isset( $entry['status'] ) ? Helper::get_string_value( $entry['status'] ) : ''; $form_data = isset( $entry['form_data'] ) ? Helper::get_array_value( $entry['form_data'] ) : []; foreach ( $block_key_map as $srfm_key ) { $field_value = $form_data[ $srfm_key ] ?? ''; $row[] = self::normalize_field_values( $field_value, $srfm_key ); } fputcsv( $stream, $row ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fputcsv } } /** * Normalize field values for CSV export. * * @param mixed $field_value Field value. * @param string $field_key Field key for field type. * * @since 2.0.0 * @return string Normalized value. */ private static function normalize_field_values( $field_value, $field_key = '' ) { /** * Filter field value for CSV normalization. * * Allows modification of field values during CSV export. This is particularly * useful for custom field types (like repeater fields in Pro) that need * special formatting for CSV export. * * @since 2.3.0 * * @param mixed $field_value The field value to normalize. * @param string $field_key The field key for identifying field type. * * @return mixed The filtered field value. Return a string to override default normalization. */ $filtered_value = apply_filters( 'srfm_normalize_csv_field_value', $field_value, $field_key ); // If filter returned a string, use it directly (custom handling was applied). if ( is_string( $filtered_value ) && $filtered_value !== $field_value ) { return $filtered_value; } // Handle arrays (multi-select, checkboxes, upload fields, etc.). if ( is_array( $field_value ) ) { // Upload field URLs are stored rawurlencode'd — decode before export. if ( str_contains( $field_key, 'srfm-upload' ) ) { $decoded_values = array_map( static function ( $val ) { return sanitize_text_field( rawurldecode( Helper::get_string_value( $val ) ) ); }, $field_value ); return implode( ', ', $decoded_values ); } return implode( ', ', array_map( 'sanitize_text_field', $field_value ) ); } return sanitize_text_field( Helper::get_string_value( $field_value ) ); } }
Save
Back