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:
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
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
ZIP Upload
Directory Upload
Auto-Rename
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 );
}
Upload individual files:
Input files: input*.txt
Output files: output*.txt
Description: desc.html
problem_controller.php:351-360
else {
if ( $up_dir ) {
$this -> _handle_test_dir_upload ( $request , $assignments_root , $up_dir , $problem_dir , $messages );
}
}
Automatically rename test files to standard format: problem_controller.php:397-423
if ( $rename_inputoutput ) {
if ( count ( $in ) != count ( $out )) {
$messages [] = "Mismatch: " . count ( $in ) . " inputs vs " . count ( $out ) . " outputs" ;
} else {
rename ( " $problem_dir /in" , " $problem_dir /in_old" );
rename ( " $problem_dir /out" , " $problem_dir /out_old" );
shell_exec ( "cd $problem_dir ; mkdir in out" );
$in = glob ( " $problem_dir /in_old/*" );
$out = glob ( " $problem_dir /out_old/*" );
for ( $i = 1 ; $i <= count ( $in ); $i ++ ) {
copy ( $in [ $i - 1 ], " $problem_dir /in/input $i .txt" );
copy ( $out [ $i - 1 ], " $problem_dir /out/output $i .txt" );
}
}
}
Test files must follow naming convention: input1.txt, output1.txt, input2.txt, output2.txt, etc.
Problem Descriptions
HTML Description
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
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
/*###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
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
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
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
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 ;
}
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 ,
]);
}
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
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
Method Route Action Permission GET /problemsList problems instructor+ GET /problems/createCreate form instructor+ POST /problemsStore problem instructor+ GET /problems/{id}/editEdit form owner, admin PUT /problems/{id}Update owner, admin DELETE /problems/{id}Delete owner, admin POST /problems/edit_description/{id}Update description owner, admin POST /problems/toggle_practice/{query}Toggle practice/share owner, admin POST /problems/edit_tags/{id}Update tags owner, admin GET /problems/export?ids=1,2,3Export ZIP instructor+ POST /problems/importImport ZIP head_instructor, admin GET /problems/downloadtestcases/{problem}/{assignment}/{type}Download tests participant
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