Skip to main content

Overview

The problem_controller handles all operations related to problems including CRUD operations, test case management, import/export functionality, and problem metadata editing.

Constructor

public function __construct()
Applies the following middleware:
  • auth - Requires user authentication
  • read_only_archive - Makes controller read-only when app is in archived mode
  • ip_white_listing - IP whitelist validation

CRUD Methods

index()

public function index(Request $request)
Displays a paginated listing of problems with filtering and search capabilities.
Search term to filter problems by name
tag_id
array
Array of tag IDs to filter problems
owners
array
Array of usernames to filter by problem owner
Access Control:
  • Admin: Can view all problems
  • Others: Can only view problems available to them
Returns: Paginated view with:
  • Problem list with assignments and languages
  • Total submissions count per problem
  • Accepted submissions count per problem
  • Acceptance ratio
  • All tags for filtering
  • All problem owners for filtering
Example from controller:
if (Auth::user()->role->name == "admin") {
    $all_problem = Problem::latest();
} else {
    $all_problem = Problem::available(Auth::user()->id)->latest();
}

// Apply filters
if ($request->get("search") != "") {
    $all_problem->where("name", "like", "%" . trim($request->get("search")) . "%");
}
if ($request->get("tag_id") != null) {
    $all_problem->whereHas("tags", function (Builder $query) use ($request) {
        $query->whereIn("tag_id", $request->get("tag_id"));
    });
}

create()

public function create()
Shows the form for creating a new problem. Access Control:
  • Admin, head_instructor, and instructor only
  • Aborts with 404 for other roles
Returns: View with:
  • All available languages (ordered by sorting)
  • Maximum file uploads limit
  • All available tags
  • Empty problem instance

store()

public function store(Request $request)
Stores a newly created problem in the database.
name
string
required
Problem name (max 255 characters)
admin_note
text
Administrative notes about the problem
allow_practice
boolean
Allow problem to appear in practice mode
sharable
boolean
Allow other instructors to use this problem
allow_input_download
boolean
Allow students to download input test cases
allow_output_download
boolean
Allow students to download output test cases
tag_id
array
Array of tag IDs or new tag names (prefixed with #)
enable
array
required
Array of 1/0 indicating which languages are enabled
language_id
array
required
Array of language IDs corresponding to enable array
time_limit
array
required
Array of time limits (seconds) for each language
memory_limit
array
required
Array of memory limits (KB) for each language
tests_zip
file
ZIP file containing test cases (in/ and out/ folders)
tests_dir
array
Array of individual test files to upload
rename_zip
boolean
Whether to rename test files sequentially when extracting ZIP
Validation Rules:
  • name: required, max 255 characters
Returns: Redirect to problems index with messages Example from controller:
$langs = [];
foreach ($request->input("enable") as $i => $lang) {
    if ($lang == 1) {
        $langs += [
            $request->input("language_id")[$i] => [
                "time_limit" => $request->input("time_limit")[$i],
                "memory_limit" => $request->input("memory_limit")[$i],
            ],
        ];
    }
}

$problem["user_id"] = Auth::user()->id;
$p = Problem::create($problem);
$p->languages()->sync($langs);

show()

public function show($id)
Attempts to display a problem directly (not through assignment). Note: This method always aborts with 404 because problems can only be viewed through assignments or practice mode.

edit()

public function edit(Problem $problem)
Shows the form for editing an existing problem.
problem
Problem
required
Problem model instance to edit
Access Control:
  • Calls _can_edit_or_404() to verify permissions
Returns: View with:
  • Problem data
  • Problem’s languages with pivot data
  • Problem’s tags
  • All available languages and tags
  • Directory tree dump of test cases
Example from controller:
$lang_of_problems = $problem->languages->keyBy("id");
$tags = $problem->tags->keyBy("id");
return view("problems.create", [
    "problem" => $problem,
    "languages" => $lang_of_problems,
    "tags" => $tags,
    "tree_dump" => shell_exec("tree -h " . $problem->get_directory_path()),
]);

update()

public function update(Request $request, Problem $problem)
Updates the specified problem in storage.
problem
Problem
required
Problem model instance to update
name
string
required
Problem name (max 255 characters)
editorial
url
URL to problem editorial
Validation Rules:
  • name: required, max 255 characters
  • editorial: nullable, must be valid URL
Process:
  1. Updates problem metadata
  2. Syncs tags (creates new tags if needed)
  3. Replaces language configurations
  4. Processes test file uploads if provided
Returns: Redirect to problems index with messages

destroy()

public function destroy($id = null)
Removes the specified problem from storage.
id
integer
required
Problem ID to delete
Access Control:
  • Admin, head_instructor, and instructor only
  • Calls _can_edit_or_404() to verify ownership
Deletion Rules:
  • Cannot delete if problem appears in assignments
  • Cannot delete if problem has submissions
Returns: JSON response:
{
  "done": 1,
  "message": "Optional error message"
}

Test Case Methods

downloadtestsdesc()

public function downloadtestsdesc($id)
Downloads the problem description file (desc.html).
id
integer
required
Problem ID
Note: Route is defined but implementation not shown in provided code. Route: /problems/downloadtestsdesc/{id}

download_testcases()

public function download_testcases(Problem $problem, Assignment $assignment, $type = null)
Downloads input or output test cases for a problem.
problem
Problem
required
Problem model instance
assignment
Assignment
required
Assignment model instance (for permission check)
type
string
required
Type of files to download: “in” or “out”
Access Control:
  • User must be able to submit to the assignment
  • Input download requires allow_input_download flag
  • Output download requires allow_output_download flag
Returns: ZIP file download with automatic deletion Route: /problems/downloadtestcases/{problem}/{assignment}/{type} Example from controller:
$check = $assignment->can_submit(Auth::user(), $problem);
if (!$check->can_submit) {
    abort(403, $check->error_message);
}

if ($type == "in" && $problem->allow_input_download == false) {
    abort(403, "This problem does not allow input download");
}

$pathdir = $assignments_root . "/problems/" . $problem->id . "/" . $type . "/";
shell_exec("cd $pathdir && zip -r $zipFile *");
return response()->download($zipFile)->deleteFileAfterSend();

Import/Export Methods

export()

public function export(Request $request)
Exports one or more problems as a ZIP file with metadata.
ids
string
required
Comma-separated list of problem IDs to export
Access Control:
  • Admin: Can export any problem
  • Others: Can only export sharable problems or their own problems
Export Includes:
  • Test cases (in/ and out/ folders)
  • Problem description (desc.html)
  • Metadata JSON file (problem.wecode.metadata.json)
  • PDF files if present
  • Templates if present
Returns: ZIP file download with automatic deletion Route: /problems/export (GET) Example from controller:
$ids = explode(",", $request->input("ids"));
$probs = Problem::whereIn("id", $ids)->get()->load("user");

if (!in_array(Auth::user()->role->name, ["admin"])) {
    $probs = $probs->reject(fn(Problem $prob, int $key)
        => !($prob->sharable && Auth::user()->role->name != "student") 
           && $prob->user->id != Auth::user()->id
    );
}
$probs->load("languages")->load("tags");

foreach ($probs as $prob) {
    $pathdir = $prob->get_directory_path();
    $metadata_file = $pathdir . "/problem.wecode.metadata.json";
    file_put_contents($metadata_file, $prob->toJSON(JSON_PRETTY_PRINT));
    shell_exec("cd $pathdir/.. && zip -r $zipFile " . $prob->id . "/*");
    unlink($metadata_file);
}

import()

public function import(Request $request)
Imports problems from a ZIP file exported by the export function.
zip_upload
file
required
ZIP file containing exported problems
Access Control:
  • Admin and head_instructor only
Validation Rules:
  • zip_upload: required
Import Process:
  1. Extracts ZIP to temporary directory
  2. Reads metadata JSON from each problem folder
  3. Creates new problem with metadata
  4. Maps language names to IDs
  5. Creates new tags if they don’t exist
  6. Copies test case files to problem directory
  7. Updates admin_note with import information
Returns: Redirect to problems index with error messages if any Route: /problems/import (POST) Example from controller:
$lang_to_id = Language::all()->pluck("id", "name");
$tag_to_id = Tag::all()->pluck('id', 'text');

foreach ($storage->directories($tmp_dir) as $prob_folder) {
    $metadata = json_decode(
        $storage->get("$prob_folder/problem.wecode.metadata.json")
    );
    
    $problem = new Problem((array) $metadata);
    $problem->id = null;
    $problem->user_id = Auth::user()->id;
    $problem->admin_note .= sprintf(
        "\nIMPORTED: original user %s(%s), updated %s",
        $metadata->user->username,
        $metadata->user->email,
        new Carbon\Carbon($metadata->updated_at)
    );
    $problem->save();
    
    // Map languages and sync
    $langs = [];
    foreach ($metadata->languages as $lang) {
        if (!isset($lang_to_id[$lang->name])) continue;
        $langs[$lang_to_id[$lang->name]] = [
            "time_limit" => $lang->pivot->time_limit,
            "memory_limit" => $lang->pivot->memory_limit,
        ];
    }
    $problem->languages()->sync($langs);
}

Description Methods

edit_description()

public function edit_description(Request $request, Problem $problem)
Updates the problem description (desc.html file).
problem
Problem
required
Problem model instance
content
text
required
HTML content for problem description
Access Control:
  • Admin, head_instructor, and instructor only
Returns: Plain text response:
  • "success" - Description saved successfully
  • "error" - Failed to save description
Route: /problems/edit_description/{problem} (POST) Example from controller:
private function save_problem_description(Problem $problem, $text, $type = "html") {
    $problem_dir = $problem->get_directory_path();
    if (file_put_contents("$problem_dir/desc.html", $text)) {
        return true;
    } else {
        return false;
    }
}

Toggle Methods

toggle_practice()

public function toggle_practice(Request $request, string $query)
Toggles the practice mode or sharable status of a problem.
query
string
required
Format: “practice.” or “sharable.
Access Control:
  • Calls _can_edit_or_404() to verify permissions
Returns: Boolean value (true/false) of new state Route: /problems/toggle_practice/{query?} (POST) Example from controller:
$a = explode(".", $query);
$task = $a[0];
$id = $a[1];

$problem = Problem::find($id);
if ($task == "practice") {
    $problem->allow_practice = !$problem->allow_practice;
    $problem->save();
    return $problem->allow_practice;
} else {
    $problem->sharable = !$problem->sharable;
    $problem->save();
    return $problem->sharable;
}

edit_tags()

public function edit_tags(Request $request, Problem $problem)
Updates the tags associated with a problem.
problem
Problem
required
Problem model instance
tag_id
array
required
Array of tag IDs or new tag names (prefixed with #)
Access Control:
  • Calls _can_edit_or_404() to verify permissions
Process:
  • Creates new tags if they don’t exist (tags prefixed with #)
  • Syncs problem’s tags with provided list
Returns: JSON response:
{
  "all_tags": [...],
  "new_tags": [...]
}
Route: /problems/edit_tags/{problem?} (POST) Example from controller:
private function _add_missing_tags($tags) {
    foreach ($tags as $i => $tag) {
        if (Tag::find($tag) == null) {
            $tag = substr($tag, 1); // Remove '#' from new tag
            if (Tag::where("text", $tag)->first() == []) {
                $new_tag = new Tag();
                $new_tag->text = $tag;
                $new_tag->save();
                $tags[$i] = (string) $new_tag->id;
            }
        }
    }
    return $tags;
}

Test File Upload

Test cases can be uploaded in two formats:

ZIP File Format

Structure:
problem.zip
├── desc.html (optional)
├── desc.pdf (optional)
├── in/
│   ├── input1.txt
│   ├── input2.txt
│   └── ...
└── out/
    ├── output1.txt
    ├── output2.txt
    └── ...
Notes:
  • Input files must be named input{N}.txt where N starts from 1
  • Output files must be named output{N}.txt matching input numbers
  • Can enable rename_zip to automatically rename files sequentially

Directory Upload Format

Upload individual files:
  • desc.html - Problem description (required)
  • input1.txt, input2.txt, … - Input test cases
  • output1.txt, output2.txt, … - Output test cases

  • Problem: Main model for problems
  • Language: Programming languages supported
  • Tag: Tags for categorizing problems
  • Assignment: Assignments that include problems
  • Submission: Student submissions for problems
  • User: Problem creator/owner

Build docs developers (and LLMs) love