Submissions API

New in version 0.9.80

The Submissions module provides a complete API for managing student assignments, file uploads, review workflows, and notifications. This guide covers the public APIs, hooks, and filters for extending submission functionality.

Checking Module Status

use BaselMedia\BricksMembers\Core\ModuleRegistry;

if ( ModuleRegistry::is_active( 'submissions' ) ) {
    // Submissions functionality available
}

Helper Functions

brm_get_submission_post_type()

Get the configured post type slug for submissions.

/**
 * @return string Post type slug (default: 'brm_submission')
 */
$post_type = brm_get_submission_post_type();

brm_get_submission_reviewer_roles()

Get the roles allowed to review submissions.

/**
 * @return array Array of role slugs
 */
$roles = brm_get_submission_reviewer_roles();
// Default: array( 'administrator' )

SubmissionsService Class

use BaselMedia\BricksMembers\Modules\Submissions\SubmissionsService;

$service = SubmissionsService::get_instance();

Creating Submissions

$submission_id = $service->create_submission(
    $user_id,     // Student user ID
    $post_id,     // Assignment post ID
    $group_id,    // Group ID or null
    'mixed',      // Type: 'file', 'text', 'url', 'mixed'
    array(
        'text'           => 'My submission text...',
        'urls'           => array( 'https://example.com/my-project' ),
        'attachment_ids' => array( 123, 456 ),
    )
);

if ( false === $submission_id ) {
    // Submission failed (permission, validation, etc.)
}

Checking Submission Permissions

// Can user submit to this assignment?
$can_submit = $service->can_submit( $user_id, $assignment_post_id );

// Can user review this submission?
$can_review = $service->can_review( $user_id, $submission_id );

// Can user view this submission?
$can_view = $service->can_view_submission( $user_id, $submission_id );

Getting Submissions

// Get a single submission
$submission = $service->get_submission( $submission_id );

// Get user's submissions for an assignment
$submissions = $service->get_user_submissions( $user_id, $assignment_post_id );

// Get all submissions for an assignment
$submissions = $service->get_assignment_submissions( $assignment_post_id, array(
    'status'   => 'submitted', // or 'approved', 'rejected', 'resubmit'
    'group_id' => $group_id,   // optional group filter
) );

// Get pending submissions for review
$pending = $service->get_pending_submissions( $reviewer_user_id );

Submission Data Structure

$submission = array(
    'id'             => 123,              // Submission post ID
    'user_id'        => 45,               // Student user ID
    'assignment_id'  => 67,               // Assignment post ID
    'group_id'       => 89,               // Group ID or null
    'type'           => 'mixed',          // file, text, url, mixed
    'status'         => 'submitted',      // submitted, approved, rejected, resubmit
    'attempt'        => 1,                // Attempt number
    'text'           => '...',            // Text content
    'urls'           => array( '...' ),   // URL array
    'attachment_ids' => array( 123 ),     // File attachment IDs
    'submitted_at'   => '2026-03-04...',  // Timestamp
    'due_at'         => '2026-03-10...',  // Due date
    'reviewed_by'    => 78,               // Reviewer user ID
    'reviewed_at'    => '2026-03-05...',  // Review timestamp
    'review_status'  => 'approved',       // Review result
    'feedback'       => '...',            // Reviewer feedback
);

Reviewing Submissions

// Approve submission
$service->review_submission( $submission_id, $reviewer_user_id, 'approved', 'Great work!' );

// Reject submission
$service->review_submission( $submission_id, $reviewer_user_id, 'rejected', 'Does not meet requirements.' );

// Request resubmission
$service->review_submission( $submission_id, $reviewer_user_id, 'resubmit', 'Please revise section 3.' );

Counting Attempts

// Get attempt count
$attempts = $service->get_user_submission_attempt_count( $user_id, $assignment_post_id, $group_id );

// Check if max attempts reached
$assignment_max = get_post_meta( $assignment_post_id, '_brm_submission_max_attempts', true );
$can_resubmit = $attempts < (int) $assignment_max;

SubmissionFileHandler Class

Handles secure file uploads and downloads:

use BaselMedia\BricksMembers\Modules\Submissions\SubmissionFileHandler;

$handler = SubmissionFileHandler::get_instance();

Uploading Files

// Process uploaded files from $_FILES
$attachment_ids = $handler->process_uploads( $submission_id, $_FILES['submission_files'] );

if ( is_wp_error( $attachment_ids ) ) {
    // Handle upload error
}

Validating Files

// Check file type
$allowed = $handler->is_allowed_file_type( 'document.pdf', $assignment_post_id );

// Check file size
$valid_size = $handler->is_valid_file_size( $file_size_bytes, $assignment_post_id );

Getting Submission Files

// Get all files for a submission
$files = $handler->get_submission_files( $submission_id );

// Get secure download URL
$download_url = $handler->get_download_url( $attachment_id, $submission_id );

SubmissionDueDateResolver Class

Calculates due dates based on assignment settings, drip rules, and groups:

use BaselMedia\BricksMembers\Modules\Submissions\SubmissionDueDateResolver;

$resolver = SubmissionDueDateResolver::get_instance();

// Get the due date for a specific user
$due_at = $resolver->resolve_assignment_due_at( $assignment_post_id, $user_id );
// Returns: DateTime string or null

// Check if submission is late
$is_late = $resolver->is_submission_late( $submission_id );

Actions (Hooks)

Submission Lifecycle

// After submission created
add_action( 'brm_event_submission_created', function( $event_data ) {
    $submission_id = $event_data['submission_id'];
    $user_id = $event_data['user_id'];
    $assignment_id = $event_data['assignment_id'];
    
    // Custom notification, analytics, etc.
} );

// After resubmission
add_action( 'brm_event_submission_resubmitted', function( $event_data ) {
    // Same structure as submission_created
} );

// After submission reviewed
add_action( 'brm_event_submission_reviewed', function( $event_data ) {
    $submission_id = $event_data['submission_id'];
    $reviewer_id = $event_data['reviewer_id'];
    $status = $event_data['status']; // approved, rejected, resubmit
    $feedback = $event_data['feedback'];
} );

File Events

// After file uploaded
add_action( 'brm_submission_file_uploaded', function( $attachment_id, $submission_id, $user_id ) {
    // Virus scan, backup, etc.
}, 10, 3 );

// After file downloaded
add_action( 'brm_submission_file_downloaded', function( $attachment_id, $submission_id, $user_id ) {
    // Track downloads
}, 10, 3 );

Filters

Submission Permissions

// Filter who can submit
add_filter( 'brm_can_submit', function( $can_submit, $user_id, $assignment_id ) {
    // Custom logic (e.g., check if user completed prerequisites)
    if ( ! user_completed_prerequisite( $user_id, $assignment_id ) ) {
        return false;
    }
    return $can_submit;
}, 10, 3 );

// Filter who can review
add_filter( 'brm_can_review_submission', function( $can_review, $user_id, $submission_id ) {
    // Allow group leaders to review their members' submissions
    // (Groups integration adds this automatically)
    return $can_review;
}, 10, 3 );

File Validation

// Custom allowed file types
add_filter( 'brm_submission_allowed_file_types', function( $types, $assignment_id ) {
    // Add custom types for specific assignments
    $types[] = 'psd';
    $types[] = 'ai';
    return $types;
}, 10, 2 );

// Custom max file size
add_filter( 'brm_submission_max_file_size', function( $bytes, $assignment_id ) {
    // Allow larger files for video assignments
    $is_video = get_post_meta( $assignment_id, '_is_video_assignment', true );
    if ( $is_video ) {
        return 100 * 1024 * 1024; // 100MB
    }
    return $bytes;
}, 10, 2 );

Notifications

// Custom notification recipients
add_filter( 'brm_submission_notification_recipients', function( $recipients, $submission_id, $type ) {
    // Add course instructor
    $assignment_id = get_post_meta( $submission_id, '_brm_submission_assignment_id', true );
    $instructor_id = get_post_meta( $assignment_id, '_course_instructor', true );
    
    if ( $instructor_id ) {
        $recipients[] = $instructor_id;
    }
    
    return $recipients;
}, 10, 3 );

// Custom notification message
add_filter( 'brm_submission_notification_message', function( $message, $submission_id, $type, $recipient_id ) {
    // Customize based on type: 'new_submission', 'review_complete', 'resubmit_request'
    return $message;
}, 10, 4 );

Due Date Calculation

add_filter( 'brm_submission_due_date', function( $due_at, $assignment_id, $user_id ) {
    // Custom due date logic
    // e.g., extend deadline for certain users
    if ( user_has_extension( $user_id, $assignment_id ) ) {
        $due_at = date( 'Y-m-d H:i:s', strtotime( $due_at . ' +3 days' ) );
    }
    return $due_at;
}, 10, 3 );

Assignment Post Meta

Assignments store configuration in post meta:

// Check if post is an assignment
$is_assignment = get_post_meta( $post_id, '_brm_is_assignment', true );

// Get assignment settings
$submission_type = get_post_meta( $post_id, '_brm_submission_type', true );
// Values: 'file', 'text', 'url', 'mixed'

$max_attempts = get_post_meta( $post_id, '_brm_submission_max_attempts', true );
// Default: 1

$due_date = get_post_meta( $post_id, '_brm_submission_due_date', true );
// Format: Y-m-d H:i:s

$allowed_extensions = get_post_meta( $post_id, '_brm_submission_file_types', true );
// Comma-separated: 'pdf,doc,docx'

$max_file_size = get_post_meta( $post_id, '_brm_submission_max_file_size', true );
// In bytes

Submission Post Meta

// Core submission data
$assignment_id = get_post_meta( $submission_id, '_brm_submission_assignment_id', true );
$status = get_post_meta( $submission_id, '_brm_submission_status', true );
$type = get_post_meta( $submission_id, '_brm_submission_type', true );
$attempt = get_post_meta( $submission_id, '_brm_submission_attempt', true );
$group_id = get_post_meta( $submission_id, '_brm_submission_group_id', true );

// Submission content
$urls = get_post_meta( $submission_id, '_brm_submission_urls', true ); // JSON array
$attachments = get_post_meta( $submission_id, '_brm_submission_attachment_ids', true ); // JSON array

// Review data
$reviewed_by = get_post_meta( $submission_id, '_brm_submission_reviewed_by', true );
$reviewed_at = get_post_meta( $submission_id, '_brm_submission_reviewed_at', true );
$review_status = get_post_meta( $submission_id, '_brm_submission_review_status', true );
$feedback = get_post_meta( $submission_id, '_brm_submission_feedback', true );

// Timing
$submitted_at = get_post_meta( $submission_id, '_brm_submission_submitted_at', true );
$due_at = get_post_meta( $submission_id, '_brm_submission_due_at', true );

AJAX Endpoints

The module registers these AJAX actions:

  • wp_ajax_brm_submit_assignment - Create new submission
  • wp_ajax_brm_review_submission - Submit review
  • wp_ajax_brm_get_submission - Fetch submission data
  • wp_ajax_brm_download_submission_file - Secure file download

Making AJAX Requests

// Submit assignment (from JS)
const formData = new FormData();
formData.append('action', 'brm_submit_assignment');
formData.append('nonce', brmSubmissions.nonce);
formData.append('assignment_id', assignmentId);
formData.append('text', textContent);
formData.append('files[]', fileInput.files[0]);

const response = await fetch(brmSubmissions.ajaxUrl, {
    method: 'POST',
    body: formData
});
const data = await response.json();

Groups Integration

When both Groups and Submissions modules are active:

// Group leaders can review their members' submissions
// This is handled automatically via the brm_can_review_submission filter

// To check if a user is a group leader for the submission's author:
$submission = $service->get_submission( $submission_id );
$author_id = $submission['user_id'];
$group_id = $submission['group_id'];

if ( $group_id ) {
    $reviewer_role = brm_get_user_group_role( $group_id, $reviewer_user_id );
    $author_role = brm_get_user_group_role( $group_id, $author_id );
    
    $can_review = 'leader' === $reviewer_role && 'member' === $author_role;
}

Building Custom Submission UIs

// In a Bricks template or custom template

// Check if current post is an assignment
$is_assignment = get_post_meta( get_the_ID(), '_brm_is_assignment', true );

if ( $is_assignment && is_user_logged_in() ) {
    $service = SubmissionsService::get_instance();
    $user_id = get_current_user_id();
    $post_id = get_the_ID();
    
    // Get existing submissions
    $submissions = $service->get_user_submissions( $user_id, $post_id );
    
    // Check if can submit
    $can_submit = $service->can_submit( $user_id, $post_id );
    
    if ( $can_submit ) {
        // Render submission form
    }
    
    if ( ! empty( $submissions ) ) {
        // Render submission history
        foreach ( $submissions as $submission ) {
            echo 'Attempt ' . $submission['attempt'] . ': ' . $submission['status'];
        }
    }
}

Best Practices

  1. Always verify permissions before creating or reviewing submissions
  2. Use the service layer rather than direct post meta for complex operations
  3. Handle file uploads securely through SubmissionFileHandler
  4. Check due dates via the resolver, not raw post meta
  5. Emit events for custom workflows (service methods do this automatically)

Related Documentation

Early Bird Deal

Start Building Your Membership Site Today

Create, sell, and manage your content without limits. BricksMembers gives you everything you need to build membership and LMS sites with Bricks Builder.

Lifetime updates & bug fixes • Premium support • 0% transaction fees • 60-day money-back guarantee