Passed
Push — master ( b831b6...96f132 )
by John
06:04 queued 21s
created

AntiCheat::handle()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 47
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 6
eloc 33
nc 10
nop 0
dl 0
loc 47
rs 8.7697
c 1
b 0
f 1
1
<?php
2
3
namespace App\Jobs;
4
5
use Illuminate\Support\Arr;
6
use Illuminate\Bus\Queueable;
7
use Illuminate\Queue\SerializesModels;
8
use Illuminate\Queue\InteractsWithQueue;
9
use Illuminate\Contracts\Queue\ShouldQueue;
10
use Illuminate\Foundation\Bus\Dispatchable;
11
use App\Models\Eloquent\ContestModel as EloquentContestModel;
12
use App\Models\Eloquent\UserModel as EloquentUserModel;
13
use Imtigger\LaravelJobStatus\Trackable;
14
use KubAT\PhpSimple\HtmlDomParser;
15
use PhpZip\ZipFile;
16
use MOSS\MOSS;
17
use Storage;
18
use Str;
19
20
class AntiCheat implements ShouldQueue
21
{
22
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Trackable;
0 ignored issues
show
introduced by
The trait Illuminate\Queue\SerializesModels requires some properties which are not provided by App\Jobs\AntiCheat: $id, $relations, $class, $keyBy
Loading history...
Bug introduced by
The trait Imtigger\LaravelJobStatus\Trackable requires the property $id which is not provided by App\Jobs\AntiCheat.
Loading history...
23
24
    public $tries = 1;
25
    public $progressVal=40;
26
    public $stepVal=0;
27
    protected $cid;
28
    protected $supportLang=[
29
        'c'=>'c',
30
        'cpp'=>'cc',
31
        'java'=>'java'
32
    ];
33
34
    /**
35
     * Create a new job instance.
36
     *
37
     * @return void
38
     */
39
40
    public function __construct($cid)
41
    {
42
        $this->prepareStatus();
43
        $this->cid=$cid;
44
        $this->setProgressMax(100);
45
    }
46
47
    /**
48
     * Execute the job.
49
     *
50
     * @return void
51
     */
52
    public function handle()
53
    {
54
        $cid=$this->cid;
55
        $contest=EloquentContestModel::find($cid);
56
57
        if(!$contest->isJudgingComplete()) throw new Exception('Judging Incompleted');
0 ignored issues
show
Bug introduced by
The type App\Jobs\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
58
59
        $acceptedSubmissions=$contest->submissions->whereIn('verdict', [
60
            'Accepted',
61
            'Partially Accepted'
62
        ]);
63
64
        $probIndex=$contest->problems->pluck('ncode', 'pid')->all();
65
        Storage::deleteDirectory("contest/anticheat/$cid/");
66
        sleep(1);
67
        $this->setProgressNow(20);
68
        $totMOSS=0;
69
70
        foreach($acceptedSubmissions as $submission){
71
            $lang=$submission->compiler->lang;
72
            if(Arr::has($this->supportLang, $lang)){
73
                $prob=$probIndex[$submission->pid];
74
                $lang=$this->supportLang[$lang];
75
                $ext=$lang;
76
                Storage::put("contest/anticheat/$cid/raw/$prob/$lang/[$submission->uid][$submission->sid].$ext", $submission->solution);
77
                $probLangs[$prob][$lang]=true;
78
                $totMOSS++;
79
            }
80
        }
81
82
        $this->setProgressNow(40);
83
        $this->stepVal=50/$totMOSS*3;
84
        $this->progressVal=40;
85
86
        foreach($probLangs as $prob=>$langs){
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $probLangs does not seem to be defined for all execution paths leading up to this point.
Loading history...
87
            foreach($langs as $lang=>$availableVal){
88
                $this->detectPlagiarism([
89
                    'lang'=>$lang,
90
                    'cid'=>$cid,
91
                    'prob'=>$prob,
92
                    'comment'=>"Contest #$cid Problem $prob Language $lang Code Plagiarism Check",
93
                ]);
94
            }
95
        }
96
        $this->setProgressNow(90);
97
        $this->finalizeReport($probLangs);
98
        $this->setProgressNow(100);
99
    }
100
101
    private function incProgress(){
102
        $this->progressVal+=$this->stepVal;
103
        $this->setProgressNow(intval($this->progressVal));
104
    }
105
106
    private function detectPlagiarism($config)
107
    {
108
        $userid = config('moss.userid');
109
        $lang=$config['lang'];
110
        $cid=$config['cid'];
111
        $prob=$config['prob'];
112
        $comment=$config['comment'];
113
        $moss = new MOSS($userid);
114
        $moss->setLanguage($lang);
115
        $moss->addByWildcard(storage_path("app/contest/anticheat/$cid/raw/$prob/$lang/*"));
116
        $moss->setCommentString($comment);
117
        $id=$moss->send();
118
        $this->incProgress();
119
        $moss->saveTo(storage_path("app/contest/anticheat/$cid/report/$prob/$lang"), $id);
120
        $this->incProgress();
121
        $this->afterWork($cid,$prob,$lang);
122
        $this->incProgress();
123
    }
124
125
    private function afterWork($cid,$prob,$lang)
126
    {
127
        $rawPath="contest/anticheat/$cid/raw/$prob/$lang";
128
        $reportPath="contest/anticheat/$cid/report/$prob/$lang";
129
        $generalPage=HtmlDomParser::str_get_html(Storage::disk('local')->get("$reportPath/index.html"), true, true, DEFAULT_TARGET_CHARSET, false);
130
        $table=$generalPage->find('table', 0);
131
        if(is_null($table)) return;
132
        foreach($table->find('tr') as $tr){
133
            if(Str::contains($tr->outertext, '<th>')) continue;
134
            $firstUID=null;
135
            foreach($tr->find('a') as $a){
136
                $a->innertext=explode("$rawPath/",$a->plaintext)[1];
137
                $a->innertext=str_replace(".$lang (",' (',$a->plaintext);
138
                [$uid,$sid,$percent]=sscanf($a->innertext,"[%d][%d] (%d");
139
                if($firstUID==$uid){
140
                    $tr->outertext='';
141
                    break;
142
                }
143
                $firstUID=$uid;
144
                $username=EloquentUserModel::find($uid)->name;
145
                $a->innertext="$sid. [$prob][$username][$percent%]";
146
            }
147
        }
148
        Storage::disk('local')->put("$reportPath/index.html",$table->outertext);
149
    }
150
151
    private function finalizeReport($probLangs)
152
    {
153
        $cid=$this->cid;
154
        $generalPage="<table><tr><th>File 1</th><th>File 2</th><th>Lines Matched</th></tr>";
155
        $index=0;
156
        foreach($probLangs as $prob=>$langs){
157
            foreach($langs as $lang=>$availableVal){
158
                $probPage=HtmlDomParser::str_get_html(Storage::disk('local')->get("contest/anticheat/$cid/report/$prob/$lang/index.html"), true, true, DEFAULT_TARGET_CHARSET, false);
159
                $table=$probPage->find('table', 0);
160
                if(is_null($table)) continue;
161
                foreach($table->find('tr') as $tr){
162
                    if(Str::contains($tr->outertext, '<th>')) continue;
163
                    $submissionA=$tr->children(0)->children(0);
164
                    $submissionB=$tr->children(1)->children(0);
165
                    $linesMatch=$tr->children(2);
166
                    [$subIndex]=sscanf($submissionA->href, "match%d.html");
167
                    Storage::disk('local')->put("contest/anticheat/$cid/report/final/match$index.html", '
168
                        <frameset cols="50%,50%" rows="100%"><frame src="match'.$index.'-0.html" name="0"><frame src="match'.$index.'-1.html" name="1"></frameset>
169
                    ');
170
                    Storage::disk('local')->copy("contest/anticheat/$cid/report/$prob/$lang/match$subIndex-0.html", "contest/anticheat/$cid/report/final/match$index-0.html");
171
                    Storage::disk('local')->copy("contest/anticheat/$cid/report/$prob/$lang/match$subIndex-1.html", "contest/anticheat/$cid/report/final/match$index-1.html");
172
                    $generalPage.="
173
                        <tr>
174
                            <td><a href=\"match$index.html\">$submissionA->plaintext</a></td>
175
                            <td><a href=\"match$index.html\">$submissionB->plaintext</a></td>
176
                            <td align=right>$linesMatch->plaintext</td>
177
                        </tr>
178
                    ";
179
                    $index++;
180
                }
181
            }
182
        }
183
        $generalPage.="</table>";
184
        Storage::disk('local')->put("contest/anticheat/$cid/report/final/index.html", $generalPage);
185
        $zip=new ZipFile();
186
        $zip->addDir(storage_path("app/contest/anticheat/$cid/report/final/"))->saveAsFile(storage_path("app/contest/anticheat/$cid/report/report.zip"))->close();
187
    }
188
189
    public function failed()
190
    {
191
192
    }
193
}
194