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

Revision 1169, 15.6 KB checked in by wefgef, 3 months ago (diff)

Makes all solved tasks visible in archive rounds.

REVIEW URL: http://reviewboard.infoarena.ro/r/195/

  • 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/round.php");
5require_once(IA_ROOT_DIR."common/parameter.php");
6require_once(IA_ROOT_DIR."common/rating.php");
7require_once(IA_ROOT_DIR."common/cache.php");
8
9// Updates a user's rating and deviation
10function score_update_rating($user_id, $round_id, $deviation, $rating)
11{
12    $query = "INSERT INTO `ia_rating` (`user_id`, `round_id`, `deviation`, `rating`)
13             VALUES (".implode(',',
14             array(db_quote($user_id),
15                 db_quote($round_id),
16                 db_quote($deviation),
17                 db_quote($rating)
18             )).") ON DUPLICATE KEY UPDATE ".
19                 "`rating` = ". $rating .",
20                 `deviation` = ".$deviation;
21    db_query($query);
22    return db_affected_rows();
23}
24
25// Updates user's task score
26function score_update($user_id, $task_id, $round_id, $value) {
27    log_assert(is_user_id($user_id), "Bad user id '$user_id'");
28    log_assert(is_task_id($task_id), "Bad task id '$task_id'");
29    log_assert(is_round_id($round_id), "Bad round id '$round_id'");
30
31    // Add user_id score for task_id at round_id to cache
32    mem_cache_set("user-task-round:".$user_id."-".$task_id."-".$round_id, (int)$value);
33
34    // Update user_id score for task_id at round_id
35    $query = "INSERT INTO `ia_score_user_round_task` (`user_id`, `round_id`, `task_id`, `score`)
36            VALUES (".implode(',',
37            array(db_quote($user_id),
38                db_quote($round_id),
39                db_quote($task_id),
40                db_quote($value)
41            )).") ON DUPLICATE KEY UPDATE
42                `score` = ".db_quote($value);
43    db_query($query);
44
45    // update score for round_id
46    $subquery = "( SELECT SUM(`score`) AS 'score' FROM `ia_score_user_round_task`
47            WHERE
48                `round_id` = ".db_quote($round_id)." &&
49                `user_id` = ".db_quote($user_id)."
50            GROUP BY `user_id` )";
51
52    $query = "INSERT INTO `ia_score_user_round` (`user_id`, `round_id`, `score`)
53            VALUES (".implode(',',
54            array(db_quote($user_id),
55                db_quote($round_id),
56                $subquery
57            )).") ON DUPLICATE KEY UPDATE
58                `score` = ".$subquery;
59    db_query($query);
60}
61
62// Builds a where clause for a score query.
63// Returns an array of conditions; you should do something like
64// join($where, ' AND ');
65function score_build_where_clauses($user, $task, $round)
66{
67    $where = array();
68
69    if ($user != null) {
70        if (is_array($user) && count($user) > 0) {
71            $where[] = "(`user_id` IN (" . db_escape_array($user) . "))";
72        } else if (is_string($user)) {
73            $where[] = sprintf("(`user_id` == '%s')", $user);
74        }
75    }
76    if ($task != null) {
77        if (is_array($task) && count($task) > 0) {
78            $where[] = "(`task_id` IN (" . db_escape_array($task) . "))";
79        } else if (is_string($task)) {
80            $where[] = sprintf("(`task_id` = '%s')", $task);
81        }
82    }
83    if ($round != null) {
84        if (is_string($round)) {
85            $round = array($round);
86        }
87
88        if (is_array($round)) {
89            if (count($round) > 0) {
90                $where[] = "(`round_id` IN (" . db_escape_array($round) . "))";
91            } else {
92                $where[] = "(TRUE = FALSE)";
93            }
94        }
95    }
96
97    return $where;
98}
99
100// Count function to be used for score_get_rankings
101function score_get_count($user, $task, $round) {
102    $where = score_build_where_clauses($user, $task, $round);
103    if (count($where) == 0) {
104        return 0;
105    }
106    $query = sprintf("SELECT COUNT(DISTINCT user_id) AS `cnt`
107            FROM ia_score_user_round
108            WHERE %s",
109            join($where, " AND "));
110    $res = db_fetch($query);
111    return $res['cnt'];
112}
113
114// Return rating history for given user_id (not username).
115// Output array format is:
116//  array(
117//      round_id =>
118//          array(
119//              rating => (int)
120//              deviation => (int)
121//              timestamp => (int UNIX timestamp)
122//              round_page_name => (string)
123//              round_title => (string)
124//          ),
125//      ...
126//  );
127// Rounds are ordered in chronological order.
128function rating_history($user_id) {
129    log_assert(is_whole_number($user_id));
130
131    // get round list, chronologically ordered
132    $history = rating_rounds();
133
134    // get user scores
135    $query = sprintf("SELECT * FROM `ia_rating`
136                      LEFT JOIN ia_round ON round_id = ia_round.id
137                      WHERE ia_rating.user_id = '%s'
138                            AND ia_round.state = 'complete'
139                     ", db_escape($user_id));
140    $rows = db_fetch_all($query);
141
142    foreach ($rows as $row) {
143        $round_id = $row['round_id'];
144        $history[$round_id]['rating'] = $row['rating'];
145        $history[$round_id]['deviation'] = $row['deviation'];
146    }
147
148    // filter out rows which user has not participated in
149    foreach (array_keys($history) as $round_id) {
150        if (!isset($history[$round_id]['rating'])) {
151            unset($history[$round_id]);
152        }
153    }
154
155    // pretty much done
156    return $history;
157}
158
159// Returns all COMPLETED rounds in chronological order that have ratings
160// enabled.
161//
162// Output array format is:
163//  array(
164//      round_id =>
165//          array(
166//              timestamp => (int UNIX timestamp)
167//              round_page_name => (string)
168//              round_title => (string)
169//          ),
170//      ...
171//  );
172function rating_rounds() {
173    $query = "SELECT
174               object_id AS round_id, `value` AS `timestamp`,
175               ia_round.page_name AS round_page_name,
176               ia_round.title AS round_title
177        FROM `ia_parameter_value`
178        LEFT JOIN ia_round ON ia_round.id = ia_parameter_value.object_id
179        WHERE parameter_id = 'rating_timestamp' AND object_type = 'round'
180              AND NOT ia_round.id IS NULL AND ia_round.`state` = 'complete'
181        ORDER BY `timestamp`, round_id
182    ";
183    $rows = db_fetch_all($query);
184    $rounds = array();
185    foreach ($rows as $row) {
186        $rounds[$row['round_id']] = array(
187            'timestamp' => $row['timestamp'],
188            'round_page_name' => $row['round_page_name'],
189            'round_title' => $row['round_title']
190        );
191    }
192
193    // filter out rounds having rating_update off
194    $query = "SELECT object_id AS round_id, parameter_id, `value`
195        FROM `ia_parameter_value`
196        WHERE parameter_id = 'rating_update' AND object_type = 'round'
197    ";
198    $rows = db_fetch_all($query);
199    foreach ($rows as $row) {
200        $round_id = $row['round_id'];
201        if (!isset($rounds[$round_id])) {
202            log_warn("Round {$round_id} has rating_update but no "
203                      ."rating_timestamp parameter!");
204            unset($rounds[$round_id]);
205            continue;
206        }
207        $value = parameter_decode($row['parameter_id'], $row['value']);
208        if ($value) {
209            $rounds[$round_id]['rating_update'] = true;
210            continue;
211        }
212
213        // Round parameters say round does not affect rating
214        unset($rounds[$round_id]);
215    }
216    foreach ($rounds as $round_id => $round) {
217        if (!isset($round['rating_update'])) {
218            unset($rounds[$round_id]);
219        }
220    }
221
222    return $rounds;
223}
224
225// Init user array with last rating, deviation & timestamp
226// Output array format is:
227//  array(
228//      username => array(
229//                      rating => (int),
230//                      deviation => (int),
231//                      timestamp => (int)
232//                  )
233//      ...
234//  );
235//
236// NOTE: This array does not contain users never rated!
237//
238// WARNING: This function is VERY RESOURCE INTENSIVE! Don't use it in
239// normal website operations.
240//
241// Last user ratings (but no deviation / timestamp) is stored directly in
242// table ia_user.
243function rating_last_scores() {
244    // FIXME: horrible query
245    $query = "SELECT
246        ia_rating.rating AS `rating`, ia_rating.deviation AS deviation,
247               ia_rating.user_id, ia_rating.round_id,
248               pv.`value` AS `timestamp`, ia_user.username
249        FROM ia_rating
250        LEFT JOIN ia_parameter_value AS pv
251            ON pv.object_type = 'round' AND pv.object_id = ia_rating.round_id
252            AND pv.parameter_id = 'rating_timestamp'
253        LEFT JOIN ia_user ON ia_user.id = ia_rating.user_id
254        ORDER BY `timestamp` DESC, ia_rating.round_id DESC
255    ";
256    $rows = db_fetch_all($query);
257
258    $users = array();
259    foreach ($rows as $row) {
260        $username = $row['username'];
261        if (isset($users[$username])) {
262            continue;
263        }
264
265        $users[$username] = array(
266                'rating' => $row['rating'],
267                'deviation' => $row['deviation'],
268                'timestamp' => $row['timestamp']);
269    }
270
271    return $users;
272}
273
274// Return current rating distribution based on cached ratings.
275// NOTE: $bucket_size refers to the absolute rating stored in database
276// (ranging to around ~2500).
277//
278// Output array format:
279//  array(
280//      13 => <count>,
281//      14 => <count>,
282//      20 => <count>,
283//      ...
284//  );
285// Key X corresponds to rating bucket [ x*$bucket_size; $bucket_size )
286// NOTE: Some buckets may be missing completely
287function rating_distribution($bucket_size) {
288    log_assert(is_numeric($bucket_size));
289    $query = "SELECT
290            COUNT(*) AS `count`,
291            FLOOR(rating_cache/{$bucket_size}) AS `bucket`
292        FROM ia_user
293        WHERE 0 < rating_cache
294        GROUP BY `bucket`
295        ORDER BY rating_cache
296    ";
297    $rows = db_fetch_all($query);
298
299    $buckets = array();
300    foreach ($rows as $row) {
301        $buckets[$row['bucket']] = $row['count'];
302    }
303
304    return $buckets;
305}
306
307// Get top rated users list.
308function get_users_by_rating_range($start, $count, $with_rankings = false)
309{
310    $query = "SELECT *
311        FROM ia_user
312        WHERE rating_cache > 0
313        AND security_level != 'admin'
314        ORDER BY rating_cache DESC
315        LIMIT %s, %s
316    ";
317    $query = sprintf($query, $start, $count);
318    $rows = db_fetch_all($query);
319
320    if ($with_rankings && count($rows)) {
321        $query = sprintf("SELECT `rating_cache` AS rating_cache
322                          FROM ia_user
323                          WHERE rating_cache > 1.5 + 3 * ROUND(%s/3)
324                          AND security_level != 'admin'",
325                         $rows[0]['rating_cache']);
326
327        $users_before = db_num_rows(db_query($query));
328
329
330        $rows[0]['position'] = $users_before + 1;
331        $equal_scores = $start - $users_before + 1;
332        for ($i = 1; $i < count($rows); ++$i) {
333            $last_row = $rows[$i - 1];
334            $row =& $rows[$i];
335            if (rating_scale($row['rating_cache']) == rating_scale($last_row['rating_cache'])) {
336                $row['position'] = $last_row['position'];
337                $equal_scores = $equal_scores + 1;
338            }
339            else {
340                $row['position'] = $last_row['position'] + $equal_scores;
341                $equal_scores = 1;
342            }
343        }
344    }
345
346    return $rows;
347}
348
349// Count function for get_users_by_rating_range.
350function get_users_by_rating_count() {
351    $query = "SELECT COUNT(*) AS `cnt`
352        FROM `ia_user`
353        WHERE `rating_cache` > 0
354        AND `security_level` != 'admin'";
355    $res = db_fetch($query);
356    return $res['cnt'];
357}
358
359// Clears ALL user ratings & rating history
360function rating_clear() {
361    db_query("DELETE FROM ia_rating");
362    db_query("UPDATE ia_user SET rating_cache = NULL");
363}
364
365// Computes rankings for $rounds
366// returns entries from start to count
367// if detail_task == true, extra columns for each task will be created
368// if detail_round == true, extra columns for each round will be created
369function score_get_rankings($rounds, $tasks, $start = 0, $count = 999999,
370                            $detail_task = false, $detail_round = false) {
371    $where = score_build_where_clauses(null, null, $rounds);
372    if (count($where) == 0) {
373        return array();
374    }
375
376    // Get the total score for all rounds
377    $query = "
378        SELECT ".(count($rounds) > 1 ? "SUM(score) AS score" : "score").",
379                user_id, ia_user.username AS user_name,
380                ia_user.full_name AS user_full,
381                ia_user.rating_cache AS user_rating
382        FROM ia_score_user_round
383        LEFT JOIN ia_user ON ia_user.id = ia_score_user_round.user_id
384        WHERE".implode('AND', $where)."
385        ".(count($rounds) > 1 ? "GROUP BY `user_id`" : "")."
386        ORDER BY score DESC
387        LIMIT ".db_escape($start).", ".db_escape($count);
388
389    $rankings = db_fetch_all($query);
390    if (count($rankings) == 0) {
391        return $rankings;
392    }
393
394    $users = array();
395    foreach ($rankings as $ranking) {
396        array_push($users, $ranking['user_id']);
397    }
398
399    // Further queries concern only the users that are in this rankings page
400    $filter_users = score_build_where_clauses($users, null, null);
401    $where[] = $filter_users[0];
402
403    // Detailed scores are mapped in an array with the following form
404    // Array[user_id][object_id] = user score for object
405    // Object can be round or task
406
407    if ($detail_round == true) {
408        // Get scores for each round
409        $query = "SELECT round_id, user_id, score
410                FROM ia_score_user_round
411                WHERE ".implode('AND', $where);
412        $scores = db_fetch_all($query);
413        foreach ($scores as $score) {
414            $user_id = $score['user_id'];
415            $round_id = $score['round_id'];
416            $rscore = $score['score'];
417            $round_scores[$user_id][$round_id] = $rscore;
418        }
419    }
420
421    if ($detail_task == true) {
422        // Get scores for each task
423        $query = "SELECT task_id, user_id, score
424                FROM ia_score_user_round_task
425                WHERE ".implode('AND', $where);
426        $scores = db_fetch_all($query);
427        foreach ($scores as $score) {
428            $user_id = $score['user_id'];
429            $task_id = $score['task_id'];
430            $tscore = $score['score'];
431            $task_scores[$user_id][$task_id] = $tscore;
432        }
433    }
434
435    // Compute rank for the first entry
436    $top_score = $rankings[0]['score'];
437    $where = score_build_where_clauses(null, null, $rounds);
438    $query = "SELECT SUM(score) AS score
439                FROM ia_score_user_round
440                WHERE ".implode(' AND ', $where)."
441                GROUP BY user_id
442                HAVING score > ".db_quote($top_score);
443    $first_rank = db_num_rows(db_query($query)) + 1;
444
445    //create all entries
446    for ($i = 0; $i < count($rankings); $i++) {
447        $user_id = $rankings[$i]['user_id'];
448
449        //task columns
450        if ($detail_task == true) {
451            foreach ($tasks as $task_id) {
452                if (isset($task_scores[$user_id][$task_id])) {
453                    $score = $task_scores[$user_id][$task_id];
454                } else {
455                    $score = 0;
456                }
457                $rankings[$i][$task_id] = $score;
458            }
459        }
460
461        //round columns
462        if ($detail_round == true) {
463            foreach ($rounds as $round_id) {
464                if (isset($round_scores[$user_id][$round_id])) {
465                    $score = $round_scores[$user_id][$round_id];
466                } else {
467                    $score = 0;
468                }
469                $rankings[$i][$round_id] = $score;
470            }
471        }
472
473        if ($i == 0) {
474            $rankings[$i]['ranking'] = $first_rank;
475            continue;
476        }
477
478        // Users with the same score should be on the same rank
479        if ($rankings[$i]['score'] == $rankings[$i - 1]['score']) {
480            $rankings[$i]['ranking'] = $rankings[$i - 1]['ranking'];
481        } else {
482            $rankings[$i]['ranking'] = $start + $i + 1;
483        }
484    }
485
486    return $rankings;
487}
488
489
490?>
Note: See TracBrowser for help on using the repository browser.