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 Symfony\Component\Process\Exception\ProcessFailedException; |
||
12 | use App\Models\Eloquent\Contest as EloquentContestModel; |
||
13 | use App\Models\Eloquent\User; |
||
14 | use Imtigger\LaravelJobStatus\Trackable; |
||
15 | use Symfony\Component\Process\Process; |
||
16 | use KubAT\PhpSimple\HtmlDomParser; |
||
17 | use PhpZip\ZipFile; |
||
18 | use Storage; |
||
19 | use Str; |
||
20 | use Log; |
||
21 | |||
22 | class AntiCheat implements ShouldQueue |
||
23 | { |
||
24 | use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Trackable; |
||
25 | |||
26 | public $tries=1; |
||
27 | public $progressVal=40; |
||
28 | public $stepVal=0; |
||
29 | protected $cid; |
||
30 | protected $retArr=[]; |
||
31 | protected $supportLang=[ |
||
32 | 'c'=>'c', |
||
33 | 'cpp'=>'c++', |
||
34 | 'java'=>'java' |
||
35 | ]; |
||
36 | |||
37 | /** |
||
38 | * Create a new job instance. |
||
39 | * |
||
40 | * @return void |
||
41 | */ |
||
42 | |||
43 | public function __construct($cid) |
||
44 | { |
||
45 | $this->prepareStatus(); |
||
46 | $this->cid=$cid; |
||
47 | $this->setProgressMax(100); |
||
48 | } |
||
49 | |||
50 | /** |
||
51 | * Execute the job. |
||
52 | * |
||
53 | * @return void |
||
54 | */ |
||
55 | public function handle() |
||
56 | { |
||
57 | $cid=$this->cid; |
||
58 | $contest=EloquentContestModel::find($cid); |
||
59 | |||
60 | if (!$contest->isJudgingComplete()) { |
||
61 | throw new Exception('Judging Incompleted'); |
||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
62 | } |
||
63 | |||
64 | $acceptedSubmissions=$contest->submissions->whereIn('verdict', [ |
||
65 | 'Accepted', |
||
66 | 'Partially Accepted' |
||
67 | ]); |
||
68 | |||
69 | $probIndex=$contest->problems->pluck('ncode', 'pid')->all(); |
||
70 | Storage::deleteDirectory("contest/anticheat/$cid/"); |
||
71 | sleep(1); |
||
72 | $this->setProgressNow(20); |
||
73 | $totProb=0; |
||
74 | $probLangs=[]; |
||
75 | |||
76 | |||
77 | foreach ($acceptedSubmissions as $submission) { |
||
78 | $lang=$submission->compiler->lang; |
||
79 | if (Arr::has($this->supportLang, $lang)) { |
||
80 | $prob=$probIndex[$submission->pid]; |
||
81 | $lang=$this->supportLang[$lang]; |
||
82 | $ext=$lang; |
||
83 | Storage::put("contest/anticheat/$cid/raw/$prob/$lang/[$submission->uid][$submission->sid].$ext", $submission->solution); |
||
84 | if (!isset($probLangs[$prob][$lang])) { |
||
85 | $probLangs[$prob][$lang]=1; |
||
86 | } else { |
||
87 | $probLangs[$prob][$lang]++; |
||
88 | } |
||
89 | $totProb++; |
||
90 | } |
||
91 | } |
||
92 | |||
93 | $this->setProgressNow(40); |
||
94 | $this->stepVal=50 / ($totProb * 2); |
||
95 | $this->progressVal=40; |
||
96 | |||
97 | foreach ($probLangs as $prob=>$langs) { |
||
98 | foreach ($langs as $lang=>$submissionCount) { |
||
99 | $this->detectPlagiarism([ |
||
100 | 'lang'=>$lang, |
||
101 | 'cid'=>$cid, |
||
102 | 'prob'=>$prob, |
||
103 | 'count'=>$submissionCount, |
||
104 | 'comment'=>"Contest #$cid Problem $prob Language $lang Code Plagiarism Check", |
||
105 | ]); |
||
106 | } |
||
107 | } |
||
108 | $this->setProgressNow(90); |
||
109 | $this->finalizeReport(); |
||
110 | $this->setProgressNow(100); |
||
111 | } |
||
112 | |||
113 | private function incProgress($factor=1) { |
||
114 | $this->progressVal+=($this->stepVal) * $factor; |
||
115 | $this->setProgressNow(intval($this->progressVal)); |
||
116 | } |
||
117 | |||
118 | private function detectPlagiarism($config) |
||
119 | { |
||
120 | $lang=$config['lang']; |
||
121 | $cid=$config['cid']; |
||
122 | $prob=$config['prob']; |
||
123 | $count=$config['count']; |
||
124 | if (strtoupper(substr(php_uname('s'), 0, 3))==='WIN') { |
||
125 | // Windows |
||
126 | $exe=base_path('binary'.DIRECTORY_SEPARATOR.'win'.DIRECTORY_SEPARATOR.'sim_'.$lang.'.exe'); |
||
127 | } else { |
||
128 | // Linux or else |
||
129 | $process=new Process(['chmod', '+x', base_path('binary'.DIRECTORY_SEPARATOR.'linux'.DIRECTORY_SEPARATOR.'sim*')]); |
||
130 | $process->run(); |
||
131 | $exe=base_path('binary'.DIRECTORY_SEPARATOR.'linux'.DIRECTORY_SEPARATOR.'sim_'.$lang); |
||
132 | } |
||
133 | |||
134 | $exec=escapeshellarg($exe).' -p '; |
||
135 | |||
136 | // wildcardly add all files |
||
137 | $exec.='*.'.$lang; |
||
138 | |||
139 | $process=new Process($exec); |
||
140 | $process->setWorkingDirectory(Storage::path('contest'.DIRECTORY_SEPARATOR.'anticheat'.DIRECTORY_SEPARATOR.$cid.DIRECTORY_SEPARATOR.'raw'.DIRECTORY_SEPARATOR.$prob.DIRECTORY_SEPARATOR.$lang)); |
||
141 | $process->run(); |
||
142 | if (!$process->isSuccessful()) { |
||
143 | Log::error("Cannot Compare Problem $prob of Contest $cid, Languages $lang"); |
||
144 | throw new ProcessFailedException($process); |
||
145 | } |
||
146 | Log::info($process->getOutput()); |
||
147 | $this->incProgress($count); |
||
148 | //afterWork |
||
149 | $this->afterWork($cid, $prob, $lang, $process->getOutput()); |
||
150 | $this->incProgress($count); |
||
151 | } |
||
152 | |||
153 | private function afterWork($cid, $prob, $lang, $rawContent) |
||
154 | { |
||
155 | foreach (preg_split('~[\r\n]+~', $rawContent) as $line) { |
||
156 | if (blank($line) or ctype_space($line)) { |
||
157 | continue; |
||
158 | } |
||
159 | // [3057][64659].c++ consists for 100 % of [3057][64679].c++ material |
||
160 | $line=explode('%', $line); |
||
161 | if (!isset($line[1])) { |
||
162 | continue; |
||
163 | } |
||
164 | [$uid1, $sid1, $percentage]=sscanf($line[0], "[%d][%d].$lang consists for %d "); |
||
165 | [$uid2, $sid2]=sscanf($line[1], " of [%d][%d].$lang material"); |
||
166 | if ($uid1==$uid2) { |
||
167 | continue; |
||
168 | } |
||
169 | $username1=User::find($uid1)->name; |
||
170 | $username2=User::find($uid2)->name; |
||
171 | $this->retArr[]=[ |
||
172 | "sub1"=>"$sid1. [$prob][$username1]", |
||
173 | "sub2"=>"$sid2. [$prob][$username2]", |
||
174 | "similarity"=>$percentage, |
||
175 | "code1"=>Storage::disk('local')->get("contest/anticheat/$cid/raw/$prob/$lang/[$uid1][$sid1].$lang"), |
||
176 | "code2"=>Storage::disk('local')->get("contest/anticheat/$cid/raw/$prob/$lang/[$uid2][$sid2].$lang"), |
||
177 | 'cid'=>$cid, |
||
178 | 'prob'=>$prob, |
||
179 | 'lang'=>$lang, |
||
180 | ]; |
||
181 | Log::info($line); |
||
182 | } |
||
183 | } |
||
184 | |||
185 | private function finalizeReport() |
||
186 | { |
||
187 | $retArr=$this->retArr; |
||
188 | usort($retArr, function($a, $b) { |
||
189 | return $b['similarity']<=>$a['similarity']; |
||
190 | }); |
||
191 | Log::debug($retArr); |
||
192 | $cid=$this->cid; |
||
193 | $index=0; |
||
194 | $generalPage="<table><tr><th>Language</th><th>Submission 1</th><th>Submission 2</th><th>Sub 1 Consists for x% of Sub 2</th></tr>"; |
||
195 | foreach ($retArr as $ret) { |
||
196 | $lang=strtoupper($ret['lang']); |
||
197 | $sub1=$ret['sub1']; |
||
198 | $sub2=$ret['sub2']; |
||
199 | $similarity=$ret['similarity']; |
||
200 | $generalPage.=" |
||
201 | <tr> |
||
202 | <td>$lang</td> |
||
203 | <td><a href=\"match$index.html\">$sub1</a></td> |
||
204 | <td><a href=\"match$index.html\">$sub2</a></td> |
||
205 | <td align=right>$similarity%</td> |
||
206 | </tr> |
||
207 | "; |
||
208 | //write match$index.html |
||
209 | $matchIndexPage='<frameset cols="50%,50%" rows="100%"><frame src="match'.$index.'-0.html" name="0"><frame src="match'.$index.'-1.html" name="1"></frameset>'; |
||
210 | Storage::disk('local')->put("contest/anticheat/$cid/report/final/match$index.html", $matchIndexPage); |
||
211 | //write two code html |
||
212 | $match0Page='<pre>'.PHP_EOL.htmlspecialchars($ret['code1']).PHP_EOL.'</pre>'; |
||
213 | Storage::disk('local')->put("contest/anticheat/$cid/report/final/match$index-0.html", $match0Page); |
||
214 | $match1Page='<pre>'.PHP_EOL.htmlspecialchars($ret['code2']).PHP_EOL.'</pre>'; |
||
215 | Storage::disk('local')->put("contest/anticheat/$cid/report/final/match$index-1.html", $match1Page); |
||
216 | $index++; |
||
217 | } |
||
218 | $generalPage.="</table>"; |
||
219 | Storage::disk('local')->put("contest/anticheat/$cid/report/final/index.html", $generalPage); |
||
220 | $zip=new ZipFile(); |
||
221 | $zip->addDir(storage_path("app/contest/anticheat/$cid/report/final/"))->saveAsFile(storage_path("app/contest/anticheat/$cid/report/report.zip"))->close(); |
||
222 | } |
||
223 | |||
224 | public function failed() |
||
225 | { |
||
226 | |||
227 | } |
||
228 | } |
||
229 |