1<?php
2/**
3 * Action Plugin: Action on Ajax requests, My submissions page and export CSV
4 *
5 * @license GPL 3 (http://www.gnu.org/licenses/gpl.html)
6 * @author  Masoud Sadrnezhaad <masoud@sadrnezhaad.ir>
7 */
8
9// must be run within Dokuwiki
10if (!defined('DOKU_INC')) {
11    die();
12}
13
14class action_plugin_judge extends DokuWiki_Action_Plugin
15{
16
17    /**
18     * Register the events
19     *
20     * @param Doku_Event_Handler $controller
21     */
22    public function register(Doku_Event_Handler $controller)
23    {
24
25        /**
26         * Submission button in top user menu bar
27         */
28        $controller->register_hook('TEMPLATE_USERTOOLS_DISPLAY', 'BEFORE', $this, 'addButton');
29
30        /**
31         * Submissions page content
32         */
33        $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'submissionsPageAction');
34        $controller->register_hook('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'submissionsPageContent');
35
36        /**
37         * Remove page cache after login
38         */
39        $controller->register_hook('AUTH_LOGIN_CHECK', 'AFTER', $this, 'removePageCache');
40
41        /**
42         * export to csv icon in submissions page
43         */
44        $controller->register_hook('TEMPLATE_PAGETOOLS_DISPLAY', 'BEFORE', $this, 'addCsvButton', array());
45        $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'exportToCSV');
46
47        /**
48         * Ajax calls
49         */
50        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'ajaxHandler');
51
52
53    }
54
55    public function addButton(Doku_Event $event)
56    {
57        if ($_SERVER['REMOTE_USER']) {
58            $event->data['items'] = array_slice($event->data['items'], 0, -1, true)
59                + array('submissions' => '<li ><a href="' . DOKU_URL . '?do=submissions" id="user_submissions" rel="nofollow" title="' . $this->getLang('btn_my_submissions') . '">' . $this->getLang('btn_my_submissions') . '</a></li>')
60                + array_slice($event->data['items'], -1, 1, true);
61        }
62    }
63
64    public function submissionsPageAction(&$event)
65    {
66        if ($event->data != 'submissions') {
67            return;
68        }
69        $event->preventDefault();
70        return true;
71    }
72
73    public function submissionsPageContent(&$event)
74    {
75        if ($event->data != 'submissions') {
76            return;
77        }
78        $event->preventDefault();
79
80        $table_html = $this->buildResultTable();
81        print $table_html;
82    }
83
84    public function buildResultTable()
85    {
86        $crud = plugin_load('helper', 'judge_crud');
87        $table_html = '
88            <h1>' . $this->getLang('programming_questions') . '</h1>
89            <div>
90                <div class="table sectionedit1">
91                    <table class="inline">
92                        <thead>
93                            <tr class="row0">
94                                <th class="col0">' . $this->getLang('count_number') . '</th><th class="col1">' . $this->getLang('question_name') . '</th><th class="col2">' . $this->getLang('timestamp') . '</th><th class="col3">' . $this->getLang('language') . '</th><th class="col4">' . $this->getLang('status') . '</th>
95                            </tr>
96                        </thead>
97                        <tbody>';
98
99        $table_html .= $crud->tableRender(array('type' => "test-case", 'user' => $_SERVER['REMOTE_USER']), "html", 1, "timestamp")["submissions_table"];
100
101        $table_html .= '
102                        </tbody>
103                    </table>
104                </div>
105            </div>';
106
107        $table_html .= '
108            <h1>' . $this->getLang('outputonly_questions') . '</h1>
109            <div>
110                <div class="table sectionedit1">
111                    <table class="inline">
112                        <thead>
113                            <tr class="row0">
114                                <th class="col0">' . $this->getLang('count_number') . '</th><th class="col1">' . $this->getLang('question_name') . '</th><th class="col2">' . $this->getLang('timestamp') . '</th><th class="col3">' . $this->getLang('status') . '</th>
115                            </tr>
116                        </thead>
117                    <tbody>';
118
119        $table_html .= $crud->tableRender(array('type' => "output-only", 'user' => $_SERVER['REMOTE_USER']), "html", 1, "timestamp")["submissions_table"];
120
121        $table_html .= '
122                    </tbody>
123                    </table>
124                </div>
125                    <input class="button" type="button" onClick="history.go(0)" value="' . $this->getLang('table_update') . '" tabindex="4" />
126            </div>';
127        return $table_html;
128    }
129
130
131    public function addCsvButton(Doku_Event $event)
132    {
133        global $ID, $ACT;
134        if ($ACT != 'submissions') {
135            return;
136        }
137        if ($event->data['view'] == 'main') {
138            $params = array('do' => 'export_csv');
139
140            // insert button at position before last (up to top)
141            $event->data['items'] = array_slice($event->data['items'], 0, -1, true) +
142                array('export_pdf' =>
143                    '<li>'
144                    . '<a href="' . wl($ID, $params) . '"  class="action export_pdf" rel="nofollow" title="' . $this->getLang('btn_export_csv') . '">'
145                    . '<span>' . $this->getLang('btn_export_csv') . '</span>'
146                    . '</a>'
147                    . '</li>'
148                ) +
149                array_slice($event->data['items'], -1, 1, true);
150        }
151    }
152
153    public function exportToCSV(&$event)
154    {
155        if ($event->data != 'export_csv') {
156            return;
157        }
158        $event->preventDefault();
159        $crud = plugin_load('helper', 'judge_crud');
160        ob_start('ob_gzhandler');
161        ob_clean();
162        $csvOutput = "#\tType\tProblem name\tTimestamp\tLanguage\tStatus\n";
163        $csvOutput .= $crud->tableRender(array('type' => "output-only", 'user' => $_SERVER['REMOTE_USER']), "csv", 1, "timestamp")["submissions_table"];
164        $csvOutput .= $crud->tableRender(array('type' => "test-case", 'user' => $_SERVER['REMOTE_USER']), "csv", 1, "timestamp")["submissions_table"];
165        print $csvOutput;
166        ob_end_flush();
167        header("Content-type: application/octet-stream");
168        header("Content-Disposition: attachment; filename=" . $_SERVER['REMOTE_USER'] . "_submissions_" . date("Y-m-d_H-i", time()) . ".csv");
169        header("Pragma: no-cache");
170        header("Expires: 0");
171    }
172
173    public function removePageCache(&$event)
174    {
175        global $config_cascade;
176        @touch(reset($config_cascade['main']['local']));
177    }
178
179    function ajaxHandler(Doku_Event $event, $param)
180    {
181        if ($event->data !== 'plugin_judge') {
182            return;
183        }
184        //no other ajax call handlers needed
185        $event->stopPropagation();
186        $event->preventDefault();
187
188        global $INPUT;
189        $task = $INPUT->str('name');
190
191        //data
192        $data = array();
193
194        switch ($task) {
195            case "submit":
196                define('DBFILE', dirname(__FILE__) . '/submissions.sqlite');
197                define('DBENGINE', 'sqlite3');
198
199                if (file_exists(DBFILE)) {
200                    $data = $this->submit_to_db();
201                }
202            case "outputonlyResult":
203                $data[] = $this->compare($INPUT->str('user_output'), $INPUT->str('problem_name'));
204                break;
205            case "resultRefresh":
206                $crud = plugin_load('helper', 'judge_crud');
207                $data[] = $crud->tableRender(array('problem_name' => $INPUT->str('problem_name'), 'type' => $INPUT->str('type'), 'user' => $INPUT->str('user')), "html", 1, "timestamp")["submissions_table"];
208                break;
209            case "judge":
210                sleep(10);
211                $run_status = rand(0, 4);
212                define('DBFILE', dirname(__FILE__) . '/submissions.sqlite');
213                $db = new SQLite3(DBFILE);
214                $query = 'UPDATE submissions SET status_code =1 WHERE submit_id = ' . $_POST['id'] . ';';
215                $db->exec($query);
216                $query = 'UPDATE submission_test_case SET valid =' . $run_status . ' WHERE submit_id = ' . $_POST['id'] . ';';
217                $db->exec($query);
218                break;
219            case "upload":
220                $data[] = $this->upload($INPUT->str('file_name'), $INPUT->str('path'), $INPUT->str('code'));
221                break;
222            case "scoreboardRefresh":
223                $crud = plugin_load('helper', 'judge_crud');
224                $i = 1;
225                $html = "";
226                foreach (explode(",",$INPUT->str('questions')) as &$problem_name) {
227                    $submissions = $crud->tableRender(array('problem_name' => $problem_name, 'type' => "test-case", 'user' => null), "html", $i, "timestamp");
228                    $html .= $submissions["submissions_table"];
229                    $i = $submissions["count"];
230                }
231                $data[] = $html;
232                break;
233        }
234
235        //json library of DokuWiki
236        $json = new JSON();
237
238        //set content type
239        header('Content-Type: application/json');
240        echo $json->encode($data);
241    }
242
243    function submit_to_db()
244    {
245
246        if (!defined('DOKU_INC')) {
247            define('DOKU_INC', dirname(__FILE__) . '/../../../../');
248            include_once DOKU_INC . 'inc/init.php';
249            include_once DOKU_INC . 'inc/plugin.php';
250        }
251
252        global $conf;
253
254        include_once dirname(__FILE__) . '/helper/jdatetime.class.php';
255        $pdate = new jDateTime(false, true, 'Asia/Tehran');
256        date_default_timezone_set('Asia/Tehran');
257        $query = 'INSERT INTO submissions VALUES (NULL,"' . time() . '","' . $_POST['problem_name'] . '","' . $_POST['user'] . '","' . $_POST['type'] . '",' . $_POST['status_code'] . ')';
258
259        $db = new SQLite3(DBFILE);
260        $db->exec($query);
261        $id = $db->lastInsertRowID();
262        $result_id_query = 'SELECT COUNT(*) FROM submissions WHERE problem_name = "' . $_POST['problem_name'] . '"AND username="' . $_POST['user'] . '"';
263        $row_number = $db->querySingle($result_id_query);
264
265        if ($conf['lang'] == "fa") {
266            $date = $pdate->date("l j F Y H:i:s");
267        } else {
268            $date = date('l j F Y H:i:s');
269        }
270
271        if ($_POST['type'] === "output-only") {
272            $result = array('date' => $date, 'row_number' => $row_number);
273        } elseif ($_POST['type'] === "test-case") {
274            $test_case_query = 'INSERT INTO submission_test_case VALUES (' . $id . ',"' . $_POST['language'] . '",' . '0,"' . $_POST['runtime'] . '")';
275            $db->exec($test_case_query);
276            $result = array('date' => $date, 'row_number' => $row_number, 'id' => $id);
277        }
278        return $result;
279    }
280
281    public function compare($user_output, $problem_name)
282    {
283        $extension = $this->loadHelper('judge_numbers');
284        $answer = $extension->parseNumber(rawWiki("داوری:" . $problem_name));
285        if ($answer == $extension->parseNumber($user_output)) {
286            return true;
287        } else {
288            return false;
289        }
290    }
291
292    public function upload($filename, $path, $code)
293    {
294        $allowedExts = array("java", "py", "c", "cpp");
295
296        if (!in_array(pathinfo(basename($filename), PATHINFO_EXTENSION), $allowedExts, true)) {
297            return $this->getLang('err_file_format');
298        }
299
300        $targetdir = $path;
301        if (substr($targetdir, -1) != "/") {
302            $targetdir .= "/";
303        }
304        if (substr($targetdir, 1) != "/") {
305            $targetdir = "/" . $targetdir;
306        }
307
308        $temp = explode(".", $filename);
309
310        if (file_exists(TARGETFILE)) {
311            return $filename . $this->getLang('err_file_exist');
312        } elseif (!file_exists($targetdir)) {
313            return $this->getLang('err_dir') . $targetdir . $this->getLang('err_upload_dir');
314        } else {
315            define('DBFILE', dirname(__FILE__) . '/submissions.sqlite');
316            $db = new SQLite3(DBFILE);
317            $query = 'SELECT submit_id FROM submissions ORDER BY submit_id DESC LIMIT 1';
318            $id = $db->exec($query);
319            define('TARGETFILE', $targetdir . $id . "." . end($temp));
320            file_put_contents(TARGETFILE, $code);
321        }
322    }
323
324}
325
326