Skip to main content

Overview

Problems are the fundamental building blocks in Wecode. Each problem contains test cases, descriptions, and configuration for automatic grading.

Problem Model

The Problem model defines the structure and behavior:
Problem.php:14
protected $fillable = [
    'id','name','diff_cmd','diff_arg','allow_practice',
    'admin_note','difficult','user_id', 'sharable','author',
    'editorial', 'allow_input_download', 'allow_output_download'
];

Key Fields

Grading

  • diff_cmd: Command for output comparison (e.g., diff, diff.rb)
  • diff_arg: Arguments for diff command (e.g., -bB)

Access Control

  • allow_practice: Students can practice outside assignments
  • sharable: Other instructors can use this problem
  • allow_input_download: Students can download test inputs
  • allow_output_download: Students can download expected outputs

Metadata

  • difficult: Problem difficulty level
  • author: Original problem author
  • editorial: URL to solution explanation
  • admin_note: Internal notes for instructors

Relationships

  • user_id: Problem owner
  • languages: Supported programming languages
  • tags: Categorization tags

Creating Problems

Route: POST /problems

Basic Problem Creation

problem_controller.php:167-229
public function store(Request $request)
{
    if (!in_array(Auth::user()->role->name, ['admin', 'head_instructor', 'instructor'])) {
        abort(404);
    }

    $validatedData = $request->validate([
        'name' => ['required', 'max:255'],
    ]);

    $tags = $request->input("tag_id");
    if ($tags != null) {
        $tags = $this->_add_missing_tags($tags);
    }

    // Configure languages with limits
    $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 = $request->input();
    $problem["user_id"] = Auth::user()->id;
    $problem["allow_practice"] = $request->has("allow_practice");
    $problem["sharable"] = $request->has("sharable");
    $problem["allow_input_download"] = $request->has("allow_input_download");
    $problem["allow_output_download"] = $request->has("allow_output_download");
    
    $p = Problem::create($problem);
    
    if ($tags != null) {
        $p->tags()->sync($tags);
    }

    $p->languages()->sync($langs);

    // Process test files upload
    $this->_take_test_file_upload($request, $p, $messages);

    return redirect()->route('problems.index');
}

Problem Structure

Directory Layout

problems/{problem_id}/
├── desc.html              # Problem description (HTML)
├── problem.pdf            # Problem description (PDF, optional)
├── in/                    # Input test cases
│   ├── input1.txt
│   ├── input2.txt
│   └── input3.txt
├── out/                   # Expected outputs
│   ├── output1.txt
│   ├── output2.txt
│   └── output3.txt
├── template.cpp           # Code template (private)
└── template.public.cpp    # Code template (public)

File Path Helper

Problem.php:22-26
public function get_directory_path(){
    $assignments_root = Setting::get("assignments_root");
    $problem_dir = $assignments_root . "/problems/".$this->id."/";
    return $problem_dir;
}

Test Cases

Uploading Test Cases

Upload a ZIP file containing:
  • in/ folder with input1.txt, input2.txt, …
  • out/ folder with output1.txt, output2.txt, …
  • desc.html for problem description
  • Optional: problem.pdf
problem_controller.php:332-349
if ($up_zip) {
    $name_zip = $request->tests_zip->getClientOriginalName();
    $path_zip = $request->tests_zip->storeAs("", $name_zip, "assignment_root");

    $this->_unload_zip_test_file($request, $assignments_root, $problem_dir, $messages, $name_zip);
}
Test files must follow naming convention: input1.txt, output1.txt, input2.txt, output2.txt, etc.

Problem Descriptions

HTML Description

Problem.php:107-123
public function description(){
    $problem_dir = $this->get_directory_path($this->id);

    $result = array(
        'description' => '<p>Description not found</p>',
        'has_pdf' => glob("$problem_dir/*.pdf") != FALSE,
        'has_template' => glob("$problem_dir/template.cpp") != FALSE
    );

    $path = "$problem_dir/desc.html";

    if (file_exists($path))
        $result['description'] = file_get_contents($path);

    return $result;
}

PDF Description

Route: GET /assignment/{assignment}/{problem}/pdf
Problem.php:74-90
public function pdf()
{
    $pattern = $this->get_directory_path()."/*.pdf";

    $pdf_files = glob($pattern);
    $pdf_files = implode("|",$pdf_files);
    if ( ! $pdf_files )
        abort(404,"File not found");

    $headers = [
        'Content-Description' => 'File Transfer',
        'Content-Type' => 'application/pdf',
    ];
    return response()->file($pdf_files, $headers);
}

Edit Description

Route: POST /problems/edit_description/{problem}
problem_controller.php:595-612
public function edit_description(Request $request, Problem $problem)
{
    if (!in_array(Auth::user()->role->name, ['admin', 'head_instructor', 'instructor'])) {
        abort(404);
    }
    if ($this->save_problem_description($problem, $request->content)) {
        echo "success";
        return;
    } else {
        echo "error";
    }
}

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;
    }
}

Code Templates

Provide starter code for students:

Template Structure

template.cpp
/*###Begin banned keyword checks
Don't use these keywords: goto, system, exec
###End banned keyword*/

#include <iostream>
using namespace std;

//###INSERT CODE HERE -
int main() {
    // Your code here
    return 0;
}

Template Retrieval

Route: POST /submissions/get_template
submission_controller.php:287-323
public function get_template(Request $request){
    $problem = $this->_creation_guard_check(
        $request->input('assignment_id'), 
        $request->input('problem_id')
    );

    $template = $problem->template_content($request->input('language_id'));

    if ($template === NULL){
        $result = array('banned' => '', 'before'  => '', 'after' => '', 'full' => '');
    } else {
        // Extract banned keywords
        preg_match("/(\/*###Begin banned.*\n)((.*\n)*)(###End banned keyword\*\/)/", $template, $matches);
        $banned = isset($matches[2]) ? $matches[2] : "";

        // Extract before/after insertion point
        preg_match("/(###End banned keyword\*\/\n)((.*\n)*)\/\/###INSERT CODE HERE -\n?((.*\n?)*)/*", $template, $matches);
        
        $before = isset($matches[2]) ? $matches[2] : "";
        $after = isset($matches[4]) ? $matches[4] : "";

        $result = array(
            'banned' => $banned, 
            'before'  => $before, 
            'after' => $after, 
            'full' => $template
        );
    }

    return response()->json($result);
}

Template Path

Problem.php:93-105
public function template_path($language_extension = 'cpp'){
    // Try public template first
    $pattern1 = rtrim($this->get_directory_path() . "/template.public." . $language_extension);
    $template_file = glob($pattern1);
    
    if ( ! $template_file ){
        // Fall back to private template
        $pattern = rtrim($this->get_directory_path() . "/template." . $language_extension);
        $template_file = glob($pattern);
    }
    return $template_file;
}

Practice Mode

Enable Practice

Problem.php:53-59
public function can_practice(User $user){
    if ($user->role->name == 'admin') return true;
    if ($user->id == $this->user->id) return true;
    if ($this->allow_practice) return true;
    if ($this->sharable &&  in_array( $user->role->name, ['head_instructor', 'instructor']) ) return true;
    return false;
}
Route: POST /problems/toggle_practice/{query}
problem_controller.php:824-843
public function toggle_practice(Request $request, string $query)
{
    $a = explode(".", $query);
    $task = $a[0];
    $id = $a[1];

    $problem = Problem::find($id);
    $this->_can_edit_or_404($problem);

    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;
    }
}

Sharable Problems

Allow other instructors to use your problems:

Availability Query

Problem.php:142-147
public static function available($user_id){
    return Problem::Where(function (Builder $query) use ($user_id) {
        $query->orWhere(['user_id' => $user_id])
            ->orWhere(['sharable' => 1]);
    });
}

Edit Permissions

Problem.php:61-71
public function can_edit(User $user){
    if ( ! in_array( $user->role->name, ['admin']) )
    {
        //Admin can always edit
        if ($this->user->id != $user->id){
            //Others can only edit problems they own
            return false;
        }
    }
    return true;
}

Tags and Categorization

Route: POST /problems/edit_tags/{problem}
problem_controller.php:844-853
public function edit_tags(Request $request, Problem $problem)
{
    $this->_can_edit_or_404($problem);
    $tags = $this->_add_missing_tags($request->input("tag_id"));
    $problem->tags()->sync($tags);
    return json_encode([
        "all_tags" => Tag::all(),
        "new_tags" => $problem->tags,
    ]);
}

Auto-create Tags

problem_controller.php:142-160
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;
            } else {
                array_splice($tags, $i, 1);
            }
        }
    }
    return $tags;
}

Language Configuration

Language Limits

Problem.php:149-152
public function languages()
{
    return $this->belongsToMany('App\Models\Language')
        ->withTimestamps()
        ->withPivot('time_limit','memory_limit');
}
Each language can have custom:
  • time_limit: Execution time in seconds
  • memory_limit: Memory limit in KB

Import/Export

Export Problems

Route: GET /problems/export?ids=1,2,3
problem_controller.php:668-708
public function export(Request $request)
{
    $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");

    $assignments_root = Setting::get("assignments_root");
    $zipFile = $assignments_root . "/problem-" . $probs->pluck("id")->implode("-") 
        . "_tests_and_descriptions_" . (string) date("Y-m-d_H-i") . ".zip";

    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  " . (string) $prob->id . "/*");
        unlink($metadata_file);
    }

    return response()->download($zipFile)->deleteFileAfterSend();
}

Import Problems

Route: POST /problems/import
problem_controller.php:710-800
public function import(Request $request)
{
    $request->validate(['zip_upload' => ['required']]);
    
    if (!in_array(Auth::user()->role->name, ["admin", "head_instructor"])) {
        abort(403, "This feature is reserved for higher up only");
    }

    $zip_file_name = $request->zip_upload->store("", "assignment_root");
    $tmp_dir = uniqid("import_tmpdir_");
    
    // Extract and process each problem
    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->save();

        // Import languages
        $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);

        // Copy files
        shell_exec("cp -r " . escapeshellarg($storage->path($prob_folder)) 
            . "/* " . $storage->path("problems/{$problem->id}/"));
    }
    
    return redirect()->route('problems.index');
}

Problem Routes

MethodRouteActionPermission
GET/problemsList problemsinstructor+
GET/problems/createCreate forminstructor+
POST/problemsStore probleminstructor+
GET/problems/{id}/editEdit formowner, admin
PUT/problems/{id}Updateowner, admin
DELETE/problems/{id}Deleteowner, admin
POST/problems/edit_description/{id}Update descriptionowner, admin
POST/problems/toggle_practice/{query}Toggle practice/shareowner, admin
POST/problems/edit_tags/{id}Update tagsowner, admin
GET/problems/export?ids=1,2,3Export ZIPinstructor+
POST/problems/importImport ZIPhead_instructor, admin
GET/problems/downloadtestcases/{problem}/{assignment}/{type}Download testsparticipant

Best Practices

Test Cases

  • Provide diverse test cases
  • Include edge cases
  • Use clear, meaningful inputs
  • Verify outputs are correct

Descriptions

  • Write clear problem statements
  • Include input/output format
  • Provide sample cases
  • Explain constraints

Configuration

  • Set appropriate time limits
  • Configure memory limits
  • Choose correct diff command
  • Enable practice mode when ready

Sharing

  • Mark quality problems as sharable
  • Document problem metadata
  • Use descriptive tags
  • Include editorial links

Build docs developers (and LLMs) love