source: trunk/common/db/round.php @ 1184

Revision 1179, 19.0 KB checked in by adrian.budau, 2 months ago (diff)

Added task security(private, protected, public) and also fixed some
problems like the fact that users could add hidden tasks to user-rounds
during contests or cache crashing during task creation.
Reviewboard URL: http://reviewboard.infoarena.ro/r/203/

  • Property svn:eol-style set to native
Line 
1<?php
2
3require_once(IA_ROOT_DIR."common/db/db.php");
4require_once(IA_ROOT_DIR."common/db/parameter.php");
5require_once(IA_ROOT_DIR."common/db/round_task.php");
6
7function _round_cache_add($round) {
8    mem_cache_set("round-by-id:{$round['id']}", $round, IA_MEM_CACHE_ROUND_EXPIRATION);
9    return $round;
10}
11
12function _round_cache_delete($round) {
13    mem_cache_delete("round-by-id:{$round['id']}");
14}
15
16// Get round
17function round_get($round_id) {
18    // this assert brakes templates pages with round_id = %round_id%
19    log_assert(is_round_id($round_id));
20
21    if (($res = mem_cache_get("round-by-id:$round_id")) !== false) {
22        return $res;
23    }
24
25    $query = sprintf("SELECT * FROM ia_round WHERE `id` = %s",
26                     db_quote($round_id));
27    return _round_cache_add(db_fetch($query));
28}
29
30// Create new round
31// Return success.
32function round_create($round, $round_params, $user_id, $remote_ip_info = null) {
33    log_assert(is_user_id($user_id));
34    log_assert_valid(round_validate($round));
35    log_assert_valid(round_validate_parameters($round['type'], $round_params));
36
37    db_insert('ia_round', $round);
38    _round_cache_delete($round);
39    $new_round = round_get($round['id']);
40
41    if ($new_round) {
42        round_update_parameters($round['id'], $round_params);
43
44        // Copy templates.
45        require_once(IA_ROOT_DIR . "common/textblock.php");
46        $replace = array("round_id" => $round['id']);
47        textblock_copy_replace("template/newround", $round['page_name'],
48                $replace, "round: {$round['id']}", $user_id, $remote_ip_info);
49
50        _round_cache_add($round);
51        return true;
52    } else {
53        _round_cache_delete($round);
54        return false;
55    }
56}
57
58function round_recompute_score($round_id) {
59    db_query("DELETE FROM ia_score_user_round
60        WHERE round_id = ".db_quote($round_id));
61    $query = "SELECT SUM(`score`) AS score, user_id
62        FROM ia_score_user_round_task
63        WHERE `round_id` = ".db_quote($round_id)."
64        GROUP BY `user_id`";
65    $rows = db_fetch_all($query);
66    if (empty($rows)) {
67        return false;
68    }
69    $query = "INSERT INTO `ia_score_user_round`
70        (`user_id`, `round_id`, `score`)
71        VALUES ";
72    $first = true;
73    foreach ($rows as $row) {
74        $user_id = $row['user_id'];
75        $score = $row['score'];
76
77        if (!$first) {
78            $query .= ", ";
79        } else {
80            $first = false;
81        }
82        $query .= sprintf("(%s, %s, %s)",
83            db_quote($user_id), db_quote($round_id), db_quote($score));
84    }
85    db_query($query);
86}
87
88// Update a round.
89function round_update($round) {
90    log_assert_valid(round_validate($round));
91    if (db_update('ia_round', $round,
92            "`id` = '".db_escape($round['id'])."'")) {
93        _round_cache_add($round);
94    } else {
95        _round_cache_delete($round);
96    }
97}
98
99// Returns array with all tasks attached to the specified round
100//
101// :WARNING: This does not select all fields related to each task,
102// but rather chooses a few.
103// Make sure that calls such as identity_require() have all necessary
104// information to yield a correct answer.
105//
106// FIXME: sensible ordering.
107// FIXME: cache tasks.
108//
109// if user_id is non-null a join is done on $score
110function round_get_tasks($round_id, $first = 0, $count = null,
111                         $user_id = null, $fetch_scores = false,
112                         $filter = null, $progress = false) {
113    if ($count === null) {
114        $count = 666013;
115    }
116    $fields = "round_task.task_id AS id, ".
117              "round_task.`order_id` AS `order`, ".
118              "task.`title` AS `title`, ".
119              "task.`page_name` AS `page_name`, ".
120              "task.`source` AS `source`, ".
121              "task.`security` AS `security`, ".
122              "task.`type` AS `type`,
123               task.`open_source` AS `open_source`,
124               task.`open_tests` AS `open_tests`,
125               task.`rating` AS `rating`";
126    if ($user_id == null || $fetch_scores == false) {
127        $query = sprintf("SELECT $fields
128                          FROM ia_round_task as round_task
129                          LEFT JOIN ia_task as task ON task.id = round_task.task_id
130                          WHERE `round_task`.`round_id` = '%s'
131                          ORDER BY round_task.`order_id` LIMIT %d, %d",
132                          db_escape($round_id), db_escape($first), db_escape($count));
133    } else {
134        $filter_clause = db_get_task_filter_clause($filter, 'score');
135        log_assert(is_whole_number($user_id));
136        $query = sprintf("SELECT $fields, score.`score` AS `score`
137                          FROM ia_round_task as round_task
138                          LEFT JOIN ia_task as task ON task.id = round_task.task_id
139                          LEFT JOIN ia_score_user_round_task as score ON
140                                score.round_id = round_task.round_id AND
141                                score.task_id = round_task.task_id AND
142                                score.user_id = '%s'
143                          WHERE `round_task`.`round_id` = '%s'
144                            AND %s
145                          ORDER BY round_task.`order_id` LIMIT %d, %d",
146                         db_escape($user_id),
147                         db_escape($round_id), db_escape($filter_clause),
148                         db_escape($first), db_escape($count));
149    }
150
151    $res = db_fetch_all($query);
152
153    // Check if we have what to progress
154    if ($progress && count($res) > 0) {
155        $task_ids = array();
156        foreach ($res as $row) {
157            $task_ids[] = $row['id'];
158        }
159
160        $query_ratings = sprintf(
161              "SELECT task_ratings.task_id AS id, count(*) AS rating_count
162               FROM ia_task_ratings AS task_ratings
163               WHERE task_ratings.task_id IN (%s)
164               GROUP BY id",
165               implode(',', array_map('db_quote', $task_ids))
166        );
167
168        $res_ratings = db_fetch_all($query_ratings);
169
170        $rating_count = array();
171        foreach ($res_ratings as $res_rating) {
172            $rating_count[$res_rating['id']] = $res_rating['rating_count'];
173        }
174
175        foreach ($res as &$row) {
176            $row['progress'] = getattr($rating_count, $row['id']);
177        }
178    }
179
180    return $res;
181}
182
183function round_get_task_count($round_id, $user_id, $filter) {
184    if ($user_id && $filter) {
185        $filter_clause = db_get_task_filter_clause($filter, 'ia_score_user_round_task');
186        $query = sprintf("SELECT COUNT(*) FROM ia_round_task " .
187                         "LEFT JOIN ia_score_user_round_task " .
188                         "ON ia_round_task.round_id = ia_score_user_round_task.round_id " .
189                         "AND ia_round_task.task_id = ia_score_user_round_task.task_id " .
190                         "AND ia_score_user_round_task.user_id = %s " .
191                         "WHERE ia_round_task.round_id = '%s' " .
192                         "AND %s",
193                         db_escape($user_id),
194                         db_escape($round_id),
195                         db_escape($filter_clause)
196                         );
197    } else {
198        $query = sprintf("SELECT COUNT(*) FROM ia_round_task
199                         WHERE `round_id` = '%s'",
200                         db_escape($round_id));
201    }
202    return db_query_value($query);
203}
204
205// Get round parameters.
206// array() if nothing found?
207function round_get_parameters($round_id) {
208    return parameter_get_values('round', $round_id);
209}
210
211// binding for parameter_update_values
212function round_update_parameters($round_id, $param_values) {
213    parameter_update_values('round', $round_id, $param_values);
214}
215
216// Replaces attached task list for given round
217// :WARNING: This function does not check for parameter validity!
218// It only stores them to database.
219//
220// $tasks is array of task id's
221function round_update_task_list($round_id, $old_tasks, $tasks,
222        $force_update_security = false, $force_check_common_tasks = false) {
223    log_assert(is_round_id($round_id));
224
225    $old_tasks_count = count($old_tasks);
226
227    // Do nothing with common tasks, be smart.
228    $common_tasks = array_intersect($old_tasks, $tasks);
229    $old_tasks = array_diff($old_tasks, $common_tasks);
230    $tasks = array_diff($tasks, $common_tasks);
231
232    // delete round-task relations
233    if (count($old_tasks) > 0) {
234        $query = sprintf("DELETE FROM ia_round_task
235                          WHERE round_id = %s AND task_id IN (%s)",
236                         db_quote($round_id),
237                         implode(',', array_map("db_quote", $old_tasks)));
238        db_query($query);
239    }
240
241    foreach ($old_tasks as $task) {
242        // Update parent round cache for old tasks
243        task_get_parent_rounds($task, true);
244        if ($force_update_security) {
245            task_update_security($task, 'check');
246        }
247    }
248
249    // Also check common tasks when forced to
250    if ($force_update_security && $force_check_common_tasks
251        && count($common_tasks) > 0) {
252        foreach ($common_tasks as $task) {
253            task_update_security($task, 'check');
254        }
255    }
256
257    if (count($tasks) > 0) {
258        // insert new relations
259        $values = array();
260        $order_id = $old_tasks_count;
261        foreach ($tasks as $task_id) {
262            $order_id += 1;
263            $values[] = "('".db_escape($round_id)."', '".
264                        db_escape($task_id)."', '".db_escape($order_id)."')";
265        }
266        $query = "INSERT INTO ia_round_task (round_id, task_id, order_id)
267                  VALUES ". implode(', ', $values);
268        db_query($query);
269        foreach ($tasks as $task) {
270            // Update parent round cache for new tasks
271            task_get_parent_rounds($task, true);
272            if ($force_update_security) {
273                task_update_security($task, 'check');
274            }
275        }
276    }
277
278    round_task_recompute_order($round_id);
279}
280
281// Returns boolean whether given user is registered to round $round_id
282function round_is_registered($round_id, $user_id) {
283    log_assert(is_round_id($round_id));
284    log_assert(is_user_id($user_id));
285
286    $query = sprintf("SELECT COUNT(*) AS `cnt` FROM ia_user_round
287                      WHERE round_id=%s AND user_id=%s",
288                     db_quote($round_id), db_quote($user_id));
289
290    $count = db_query_value($query);
291    return (0 < $count);
292}
293
294// Registers user $user_id to round $round_id
295// NOTE: This does not check for proper user permissions
296//
297// NOTE: There is a unique primary key constraint in the database for
298// the pair (round_id, user_id). Registering the same user twice
299// will fail.
300function round_register_user($round_id, $user_id) {
301    log_assert(is_round_id($round_id));
302    log_assert(is_user_id($user_id));
303    $insert_fields = array(
304        "round_id" => $round_id,
305        "user_id" => $user_id
306    );
307    return db_insert('ia_user_round', $insert_fields);
308}
309
310// Unregisters user $user_id from round $round_id
311// NOTE: This does not check if user is registered
312// Returns false if failed
313function round_unregister_user($round_id, $user_id) {
314    log_assert(is_round_id($round_id));
315    log_assert(is_user_id($user_id));
316
317    $query = sprintf("DELETE FROM `ia_user_round`".
318                     "WHERE `user_id` = %s AND `round_id` = %s",
319                     db_quote($user_id), db_quote($round_id));
320
321    db_query($query);
322    return db_affected_rows() == 1;
323}
324
325// Returs list of registred user to round $round_id, order by rating
326// round can be
327function round_get_registered_users_range($round_id, $start, $range) {
328    log_assert(is_round_id($round_id));
329    log_assert(is_whole_number($start));
330    log_assert(is_whole_number($range));
331    log_assert($start >= 0);
332    log_assert($range >= 0);
333
334    // FIXME: don't differentiate on $round['type']
335    $round = round_get($round_id);
336    $query = sprintf("SELECT user.id AS user_id, user.rating_cache AS rating,
337                      user.username AS username, user.full_name AS fullname
338                      FROM ia_user_round AS user_round
339                      LEFT JOIN ia_user AS user ON user_id = user.id
340                      WHERE round_id = '%s'
341                      ORDER BY rating DESC
342                      LIMIT %s, %s", db_escape($round_id), $start, $range);
343
344    $tab = db_fetch_all($query);
345    for ($i = 0; $i < count($tab); ++$i) {
346        $tab[$i]['position'] = $start + $i + 1;
347    }
348    return $tab;
349}
350
351// Returns number of registered users in a certain round
352function round_get_registered_users_count($round_id) {
353    log_assert(is_round_id($round_id));
354
355    // FIXME: don't differentiate on $round['type']
356    $round = round_get($round_id);
357    $query = sprintf("SELECT COUNT(*) FROM ia_user_round
358                      WHERE `round_id` = '%s'",
359                      db_escape($round_id));
360    return db_query_value($query);
361}
362
363// Makes all tasks protected from private
364// No error handling.
365function round_unhide_all_tasks($round_id) {
366    log_assert(is_round_id($round_id));
367    $tasks = round_get_tasks($round_id);
368    foreach ($tasks as $task) {
369        task_update_security($task['id'], 'protected');
370    }
371}
372
373// Makes all tasks private from protected
374// No error handling.
375function round_hide_all_tasks($round_id) {
376    log_assert(is_round_id($round_id));
377    $tasks = round_get_tasks($round_id);
378    foreach ($tasks as $task) {
379        task_update_security($task['id'], 'private');
380    }
381}
382
383// FIXME: horrible evil hack, for the eval.
384// FIXME: replace with eval queue
385// Gets the round to start, or null.
386function round_get_round_to_start() {
387    // Build duration subquery.
388    $duration_subquery = <<<SQL
389SELECT `value` FROM `ia_parameter_value`
390    WHERE `object_type` = 'round' AND
391          `object_id` = `id` AND
392          `parameter_id` = 'duration'
393    LIMIT 1
394SQL;
395
396    $query = <<<SQL
397SELECT * FROM `ia_round`
398    WHERE `state` != 'running' AND
399    `start_time` <= '%s' AND
400    DATE_ADD(`start_time`, INTERVAL ($duration_subquery) * 60 * 60 SECOND) > '%s'
401    LIMIT 1
402SQL;
403    return db_fetch(sprintf($query, db_date_format(), db_date_format()));
404}
405
406// FIXME: horrible evil hack, for the eval.
407// FIXME: replace with eval queue
408// Gets the round to stop, or null.
409// Duration is in the params, so we join. FUCK YEAH!!!
410function round_get_round_to_stop() {
411    // Build duration subquery.
412    $duration_subquery = <<<SQL
413SELECT `value` FROM `ia_parameter_value`
414    WHERE `object_type` = 'round' AND
415          `object_id` = `id` AND
416          `parameter_id` = 'duration'
417    LIMIT 1
418SQL;
419
420    // Build the main query.
421    $query = <<<SQL
422SELECT *
423    FROM `ia_round`
424    WHERE DATE_ADD(`start_time`, INTERVAL ($duration_subquery) * 60 * 60 SECOND) <= '%s'
425          AND `state` != 'complete'
426    LIMIT 1
427SQL;
428
429    return db_fetch(sprintf($query, db_date_format()));
430}
431
432// FIXME: horrible evil hack, for the eval.
433// FIXME: replace with eval queue
434// Gets the round to put in waiting, or null.
435// This is to prevent "back to the future" situations from fucking up round registration
436function round_get_round_to_wait() {
437    // Build the main query.
438    $query = <<<SQL
439SELECT *
440    FROM `ia_round`
441    WHERE `start_time` > '%s' AND `state` != 'waiting'
442    LIMIT 1
443SQL;
444    return db_fetch(sprintf($query, db_date_format()));
445}
446
447function round_get_many($options) {
448    $field_list = "`ia_round`.*";
449    $where = array();
450    if (getattr($options, "name_regexp")) {
451        $where[] = "`ia_round`.`id` REGEXP " . db_quote($options["name_regexp"]);
452    }
453    if (getattr($options, "type")) {
454        $where[] = "`ia_round`.`type` = " . db_quote($options["type"]);
455    }
456
457    // Add a join for username.
458    $join = "";
459    if (getattr($options, 'username', false) == true) {
460        $field_list .= ", `ia_user`.`username` AS `user_name`" .
461                       ", `ia_user`.`full_name` AS `user_fullname`" .
462                       ", `ia_user`.`rating_cache` AS `user_rating`";
463        $join = "LEFT JOIN ia_user ON `ia_round`.`user_id` = `ia_user`.`id`";
464    }
465
466    if (strtolower(getattr($options, "order", "desc") == "desc")) {
467        $order = "DESC";
468    } else {
469        $order = "ASC";
470    }
471
472    $limit = db_quote(getattr($options, "limit", 50));
473    $offset = db_quote(getattr($options, "offset", 0));
474
475    if (!empty($where)) {
476        $where = " WHERE (" . implode(") AND (", $where) . ")";
477    } else {
478        $where = "";
479    }
480
481    $query = "SELECT $field_list FROM `ia_round` $join $where";
482    $query .= " ORDER BY `ia_round`.`start_time` $order, `ia_round`.`id` $order";
483    $query .= " LIMIT $offset, $limit";
484
485    $rounds = db_fetch_all($query);
486
487    if (getattr($options, "get_count")) {
488        $query = "SELECT COUNT(*) FROM `ia_round` $where";
489        $rounds["count"] = db_fetch($query);
490        $rounds["count"] = array_pop($rounds["count"]);
491    }
492
493    return $rounds;
494}
495
496function round_delete($round_id) {
497    log_assert(is_round_id($round_id));
498
499    // Delete round from cache
500    _round_cache_delete($round_id);
501
502    // Delete job_tests
503    $query = sprintf("SELECT `id`
504                      FROM `ia_job`
505                      WHERE `round_id` = %s",
506                      db_quote($round_id));
507
508    $job_ids_fetched = db_fetch_all($query);
509
510    $job_ids = array();
511    foreach ($job_ids_fetched as $job) {
512        $job_ids[] = (int)$job["id"];
513    }
514
515    if (count($job_ids)) {
516        $formated_job_ids = implode(", ", array_map("db_quote", $job_ids));
517        $query = sprintf("DELETE FROM `ia_job_test`
518                          WHERE `job_id` IN (%s)",
519                          $formated_job_ids);
520        db_query($query);
521
522        $query = sprintf("DELETE FROM `ia_job`
523                          WHERE `id` IN (%s)",
524                          $formated_job_ids);
525        db_query($query);
526    }
527
528    // Delete entries from ia_parameter_value...
529    $query = sprintf("DELETE FROM `ia_parameter_value`
530                      WHERE `object_type` = 'round'
531                        AND `object_id` = %s",
532                      db_quote($round_id));
533    db_query($query);
534
535    // Delete entries from round-task
536    $query = sprintf("DELETE FROM `ia_round_task`
537                      WHERE `round_id` = %s",
538                      db_quote($round_id));
539    db_query($query);
540
541    // Delete entries from user-round
542    $query = sprintf("DELETE FROM `ia_user_round`
543                      WHERE `round_id` = %s",
544                      db_quote($round_id));
545    db_query($query);
546
547    // Delete entries from round-task
548    $query = sprintf("DELETE FROM `ia_round_task`
549                      WHERE `round_id` = %s",
550                      db_quote($round_id));
551    db_query($query);
552
553    // Delete entries from ia_score_user_round
554    $query = sprintf("DELETE FROM `ia_score_user_round`
555                      WHERE `round_id` = %s",
556                      db_quote($round_id));
557    db_query($query);
558
559    // Delete entries from ia_score_user_round_task
560    $query = sprintf("DELETE FROM `ia_score_user_round_task`
561                      WHERE `round_id` = %s",
562                      db_quote($round_id));
563    db_query($query);
564
565    // Delete entries from ia_rating
566    $query = sprintf("DELETE FROM `ia_rating`
567                      WHERE `round_id` = %s",
568                      db_quote($round_id));
569    db_query($query);
570
571    // ACTUALLY DELETE THE ROUND
572    $query = sprintf("DELETE FROM `ia_round`
573                      WHERE `id` = %s",
574                      db_quote($round_id));
575    db_query($query);
576}
Note: See TracBrowser for help on using the repository browser.