= $form_limit ) { // Get form name for better error message. $form_repo = new AFFCF_Form_Repository(); $form = $form_repo->get_by_id( $form_id ); $form_name = $form ? $form->form_name : 'this form'; return new WP_Error( 'form_limit_exceeded', sprintf( /* translators: 1: number of submissions allowed, 2: form name */ __( 'You have reached the maximum of %1$d submissions for %2$s.', 'airy-frontend-forms' ), $form_limit, $form_name ) ); } return true; } /** * Check global submission limit * * @param int $user_id User ID. * @param array $limits Limits settings. * @return bool|WP_Error. */ private static function check_global_limit( $user_id, $limits ) { if ( empty( $limits['global_limit_enabled'] ) ) { return true; // Global limit disabled. } $global_limit = (int) $limits['global_limit_count']; $current_count = (int) get_user_meta( $user_id, '_affcf_total_submissions', true ); if ( $current_count >= $global_limit ) { return new WP_Error( 'global_limit_exceeded', sprintf( /* translators: %d: total number of submissions allowed */ __( 'You have reached the maximum of %d total submissions across all forms.', 'airy-frontend-forms' ), $global_limit ) ); } return true; } /** * Check time-based limit * * @param int $user_id User ID. * @param array $limits Limits settings. * @return bool|WP_Error. */ private static function check_time_based_limit( $user_id, $limits ) { if ( empty( $limits['time_based_enabled'] ) ) { return true; // Time-based limit disabled. } $period = $limits['time_based_period'] ?? 'month'; $limit = (int) $limits['time_based_limit']; // Calculate time period. $time_periods = array( 'day' => '-1 day', 'week' => '-7 days', 'month' => '-30 days', ); $time_period = $time_periods[ $period ] ?? '-30 days'; $start_date = gmdate( 'Y-m-d H:i:s', strtotime( $time_period ) ); // Count submissions in time period. global $wpdb; $submissions_table = $wpdb->prefix . 'affcf_submissions'; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name from $wpdb->prefix, plugin-controlled $count = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$submissions_table} WHERE user_id = %d AND submission_date >= %s", $user_id, $start_date ) ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter if ( $count >= $limit ) { $period_labels = array( 'day' => __( 'today', 'airy-frontend-forms' ), 'week' => __( 'this week', 'airy-frontend-forms' ), 'month' => __( 'this month', 'airy-frontend-forms' ), ); $period_label = $period_labels[ $period ] ?? __( 'this period', 'airy-frontend-forms' ); return new WP_Error( 'time_limit_exceeded', sprintf( /* translators: 1: number of submissions allowed, 2: time period (e.g., "today", "this week") */ __( 'You have reached the maximum of %1$d submissions %2$s.', 'airy-frontend-forms' ), $limit, $period_label ) ); } return true; } /** * Increment submission counts after successful submission * * @param int $user_id User ID. * @param int $form_id Form ID. */ public static function increment_counts( $user_id, $form_id ) { // Increment per-form count. $form_count = (int) get_user_meta( $user_id, '_affcf_submission_count_' . $form_id, true ); update_user_meta( $user_id, '_affcf_submission_count_' . $form_id, $form_count + 1 ); // Increment global count. $total_count = (int) get_user_meta( $user_id, '_affcf_total_submissions', true ); update_user_meta( $user_id, '_affcf_total_submissions', $total_count + 1 ); } /** * Get remaining submissions for user * * @param int $user_id User ID. * @param int $form_id Form ID. * @return array Remaining counts. */ public static function get_remaining( $user_id, $form_id ) { $limits = get_option( 'affcf_submission_limits', array() ); $remaining = array(); // Per-form remaining. if ( ! empty( $limits['per_form_limits'][ $form_id ]['enabled'] ) ) { $form_limit = (int) $limits['per_form_limits'][ $form_id ]['limit']; $current = (int) get_user_meta( $user_id, '_affcf_submission_count_' . $form_id, true ); $remaining['form'] = max( 0, $form_limit - $current ); } // Global remaining. if ( ! empty( $limits['global_limit_enabled'] ) ) { $global_limit = (int) $limits['global_limit_count']; $current = (int) get_user_meta( $user_id, '_affcf_total_submissions', true ); $remaining['global'] = max( 0, $global_limit - $current ); } // Time-based remaining. if ( ! empty( $limits['time_based_enabled'] ) ) { $period = $limits['time_based_period'] ?? 'month'; $limit = (int) $limits['time_based_limit']; $time_periods = array( 'day' => '-1 day', 'week' => '-7 days', 'month' => '-30 days', ); $time_period = $time_periods[ $period ] ?? '-30 days'; $start_date = gmdate( 'Y-m-d H:i:s', strtotime( $time_period ) ); global $wpdb; $submissions_table = $wpdb->prefix . 'affcf_submissions'; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Table name from $wpdb->prefix, plugin-controlled $current = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$submissions_table} WHERE user_id = %d AND submission_date >= %s", $user_id, $start_date ) ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $remaining['time'] = max( 0, $limit - $current ); $remaining['period'] = $period; } return $remaining; } /** * Reset user submission counts * * @param int $user_id User ID. * @param int $form_id Optional form ID to reset specific form count. */ public static function reset_counts( $user_id, $form_id = null ) { if ( $form_id ) { // Reset specific form count. delete_user_meta( $user_id, '_affcf_submission_count_' . $form_id ); } else { // Reset all counts. global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Bulk cleanup of user submission count meta $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->usermeta} WHERE user_id = %d AND (meta_key LIKE %s OR meta_key = %s)", $user_id, $wpdb->esc_like( '_affcf_submission_count_' ) . '%', '_affcf_total_submissions' ) ); } } }