diff --git a/db/constraints.sql b/db/constraints.sql
index e32daffb21..537bda945e 100644
--- a/db/constraints.sql
+++ b/db/constraints.sql
@@ -132,3 +132,9 @@ alter table assignment
alter table job_file
add unique jf_md5(md5);
+
+alter table badge_user
+ add unique (user_id, badge_id);
+
+alter table badge_team
+ add unique(team_id, badge_id);
diff --git a/db/schema.sql b/db/schema.sql
index 063f47a9bc..b5596ac005 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -690,3 +690,37 @@ create table notify (
opaque integer not null
-- some other ID, e.g. that of the thread, user or PM record
);
+
+create table badge (
+ id serial primary key,
+ create_time double not null,
+ type tinyint not null,
+ -- 0=user, 1=team
+ name varchar(255) not null,
+ -- internal use (not visible to users)
+ title varchar(255) not null,
+ -- user-visible, short
+ description varchar(255) not null,
+ -- user-visible, possibly longer
+ image_url varchar(255) not null,
+ -- location of image
+ level varchar(255) not null,
+ -- project-defined
+ tags varchar(255) not null,
+ -- project-defined
+ sql_rule varchar(255) not null
+);
+
+create table badge_user (
+ badge_id integer not null,
+ user_id integer not null,
+ create_time double not null,
+ reassign_time double not null
+);
+
+create table badge_team (
+ badge_id integer not null,
+ team_id integer not null,
+ create_time double not null,
+ reassign_time double not null
+);
diff --git a/html/inc/boinc_db.inc b/html/inc/boinc_db.inc
index fbf5853618..afde1d0dbd 100644
--- a/html/inc/boinc_db.inc
+++ b/html/inc/boinc_db.inc
@@ -501,4 +501,72 @@ function latest_avs_app($appid) {
return $r;
}
+class BoincBadge {
+ static function enum($where_clause) {
+ $db = BoincDb::get();
+ return $db->enum('badge', 'BoincBadge', $where_clause);
+ }
+ static function insert($clause) {
+ $db = BoincDb::get();
+ $ret = $db->insert('badge', $clause);
+ if (!$ret) return 0;
+ return $db->insert_id();
+ }
+ function update($clause) {
+ $db = BoincDb::get();
+ return $db->update($this, 'badge', $clause);
+ }
+ static function lookup_id($id) {
+ $db = BoincDb::get();
+ return $db->lookup_id($id, 'badge', 'BoincBadge');
+ }
+ static function lookup($clause) {
+ $db = BoincDb::get();
+ return $db->lookup('badge', 'BoincBadge', $clause);
+ }
+}
+
+class BoincBadgeUser {
+ static function enum($where_clause) {
+ $db = BoincDb::get();
+ return $db->enum('badge_user', 'BoincBadgeUser', $where_clause);
+ }
+ static function insert($clause) {
+ $db = BoincDb::get();
+ $ret = $db->insert('badge_user', $clause);
+ if (!$ret) return false;
+ return true;
+ }
+ static function lookup($clause) {
+ $db = BoincDb::get();
+ return $db->lookup('badge_user', 'BoincBadgeUser', $clause);
+ }
+ static function update($clause) {
+ $db = BoincDb::get();
+ return $db->update_aux('badge_user', $clause);
+ }
+ function delete($clause) {
+ $db = BoincDb::get();
+ $db->delete_aux('badge_user', $clause);
+ }
+}
+
+class BoincBadgeTeam {
+ static function lookup($clause) {
+ $db = BoincDb::get();
+ return $db->lookup('badge_team', 'BoincBadgeTeam', $clause);
+ }
+ static function insert($clause) {
+ $db = BoincDb::get();
+ $ret = $db->insert('badge_team', $clause);
+ if (!$ret) return false;
+ return true;
+ }
+ function delete($clause) {
+ $db = BoincDb::get();
+ $db->delete_aux('badge_team', $clause);
+ }
+}
+
+
?>
diff --git a/html/inc/user.inc b/html/inc/user.inc
index 7245705e57..7c802f551e 100644
--- a/html/inc/user.inc
+++ b/html/inc/user.inc
@@ -396,6 +396,17 @@ function community_links($clo, $logged_in_user){
}
}
+function show_badges($user) {
+ $bus = BoincBadgeUser::enum("user_id=$user->id");
+ if (!$bus) return;
+ $x = "";
+ foreach ($bus as $bu) {
+ $badge = BoincBadge::lookup_id($bu->badge_id);
+ $x .= "title\" height=40 src=$badge->image_url> ";
+ }
+ row2("Badges", $x);
+}
+
function show_profile_link($user) {
if ($user->has_profile) {
row2(tra("Profile"), "id\">".tra("View")."");
diff --git a/html/inc/util.inc b/html/inc/util.inc
index aff315d58c..aef4ca3b15 100644
--- a/html/inc/util.inc
+++ b/html/inc/util.inc
@@ -327,7 +327,7 @@ function table_header() {
$col = func_get_arg($i);
echo "
".$col[0]." | \n";
} else {
- echo "".func_get_arg($i)." | \n";
+ echo "".func_get_arg($i)." | \n";
}
}
echo "\n";
diff --git a/html/ops/badge_admin.php b/html/ops/badge_admin.php
new file mode 100644
index 0000000000..0f7031f76a
--- /dev/null
+++ b/html/ops/badge_admin.php
@@ -0,0 +1,114 @@
+.
+
+// web interface for administering badges
+
+require_once('../inc/util_ops.inc');
+
+function show_form() {
+ start_table();
+ table_header(
+ "ID",
+ "name",
+ "type
0=user
1=team",
+ "title",
+ "description",
+ "image URL",
+ "level",
+ "tags"
+ );
+
+ $badges = BoincBadge::enum("");
+ foreach ($badges as $badge) {
+ echo "
\n";
+ }
+
+ echo "
\n";
+
+ end_table();
+}
+
+function add_badge() {
+ $name = BoincDb::escape_string(post_str("name"));
+ $type = post_int("type");
+ $title = BoincDb::escape_string(post_str("title"));
+ $description = BoincDb::escape_string(post_str("description"));
+ $image_url = BoincDb::escape_string(post_str("image_url"));
+ $level = BoincDb::escape_string(post_str("level"));
+ $tags = BoincDb::escape_string(post_str("tags"));
+ $now = time();
+ $id = BoincBadge::insert("(create_time, name, type, title, description, image_url, level, tags) values ($now, '$name', $type, '$title', '$description', '$image_url', '$level', '$tags')");
+ if (!$id) {
+ admin_error_page("Insert failed");
+ }
+}
+
+function update_badge() {
+ $id = post_int("id");
+ $badge = BoincBadge::lookup_id($id);
+ if (!$badge) {
+ admin_error_page("no such badge");
+ }
+ $name = BoincDb::escape_string(post_str("name"));
+ $type = post_int("type");
+ $title = BoincDb::escape_string(post_str("title"));
+ $description = BoincDb::escape_string(post_str("description"));
+ $image_url = BoincDb::escape_string(post_str("image_url"));
+ $level = BoincDb::escape_string(post_str("level"));
+ $tags = BoincDb::escape_string(post_str("tags"));
+ $retval = $badge->update("name='$name', type=$type, title='$title', description='$description', image_url='$image_url', level='$level', tags='$tags'");
+ if (!$retval) {
+ admin_error_page("update failed");
+ }
+}
+
+
+if (post_str('add_badge', true)) {
+ add_badge();
+} else if (post_str('update', true)) {
+ update_badge();
+}
+admin_page_head("Manage badges");
+show_form();
+admin_page_tail();
+?>
diff --git a/html/ops/badge_assign.php b/html/ops/badge_assign.php
new file mode 100755
index 0000000000..61c913147f
--- /dev/null
+++ b/html/ops/badge_assign.php
@@ -0,0 +1,105 @@
+#!/usr/bin/env php
+.
+
+// Assign badges based on RAC.
+// Customize this to grant other types of badges
+
+require_once("../inc/boinc_db.inc");
+
+define("GOLD_RAC", 100000);
+define("SILVER_RAC", 10000);
+define("BRONZE_RAC", 1000);
+
+function get_badge($name, $t, $rac, $image_url) {
+ $b = BoincBadge::lookup("name='$name'");
+ if ($b) return $b;
+ $now = time();
+ $title = "$t badge: average credit > $rac";
+ $id = BoincBadge::insert("(create_time, name, title, image_url) values ($now, '$name', '$title', 'img/$image_url')");
+ $b = BoincBadge::lookup_id($id);
+ if ($b) return $b;
+ die("can't create badge $name\n");
+}
+
+$rac_gold = get_badge("rac_gold", "Gold", GOLD_RAC, "gold.png");
+$rac_silver = get_badge("rac_silver", "Silver", SILVER_RAC, "silver.png");
+$rac_bronze = get_badge("rac_bronze", "Bronze", BRONZE_RAC, "bronze.png");
+
+function assign_badge($user, $badge) {
+ $now = time();
+ $bbu = BoincBadgeUser::lookup("user_id=$user->id and badge_id=$badge->id");
+ if ($bbu) {
+ echo "reassigning $badge->name to $user->id\n";
+ $bbu->update("reassign_time=$now where user_id=$user->id and badge_id=$badge->id");
+ } else {
+ echo "assigning $badge->name to $user->id\n";
+ BoincBadgeUser::insert("(create_time, user_id, badge_id, reassign_time) values ($now, $user->id, $badge->id, $now)");
+ }
+}
+
+function unassign_badges($user, $badges) {
+ $list = null;
+ foreach($badges as $badge) {
+ echo "unassigning $badge->name to $user->id\n";
+ if ($list) {
+ $list .= ",$badge->id";
+ } else {
+ $list = "$badge->id";
+ }
+ }
+ BoincBadgeUser::delete("user_id=$user->id and badge_id in ($list)");
+}
+
+function assign_rac_badge($user) {
+ global $rac_gold, $rac_silver, $rac_bronze;
+ if ($user->expavg_credit > GOLD_RAC) {
+ assign_badge($user, $rac_gold);
+ unassign_badges($user, array($rac_silver, $rac_bronze));
+ } else if ($user->expavg_credit > SILVER_RAC) {
+ assign_badge($user, $rac_silver);
+ unassign_badges($user, array($rac_bronze, $rac_gold));
+ } else if ($user->expavg_credit > BRONZE_RAC) {
+ assign_badge($user, $rac_bronze);
+ unassign_badges($user, array($rac_gold, $rac_silver));
+ } else {
+ unassign_badges($user, array($rac_gold, $rac_silver, $rac_bronze));
+ }
+}
+
+function assign_badges_user($user) {
+ assign_rac_badge($user);
+ // ... assign other types of badges
+}
+
+function assign_badges() {
+ $n = 0;
+ $maxid = BoincUser::max("id");
+ while ($n <= $maxid) {
+ $m = $n + 1000;
+ $users = BoincUser::enum_fields("id, expavg_credit", "id>=$n and id<$m and total_credit>0");
+ foreach ($users as $user) {
+ assign_badges_user($user);
+ }
+ $n = $m;
+ }
+}
+
+assign_badges();
+
+?>
diff --git a/html/ops/index.php b/html/ops/index.php
index 3d2cb93a42..c474d67e29 100644
--- a/html/ops/index.php
+++ b/html/ops/index.php
@@ -114,6 +114,7 @@ echo "
User management
- Screen user profiles
+ - Badges
- User privileges
- User job submission privileges
- Send mass email to a selected set of users
diff --git a/html/user/img/bronze.png b/html/user/img/bronze.png
new file mode 100644
index 0000000000..fc7525f2e7
Binary files /dev/null and b/html/user/img/bronze.png differ
diff --git a/html/user/img/gold.png b/html/user/img/gold.png
new file mode 100644
index 0000000000..f1521de393
Binary files /dev/null and b/html/user/img/gold.png differ
diff --git a/html/user/img/silver.png b/html/user/img/silver.png
new file mode 100644
index 0000000000..4fa06497db
Binary files /dev/null and b/html/user/img/silver.png differ
diff --git a/html/user/show_user.php b/html/user/show_user.php
index c7d9fa1a8f..c2ff2d0770 100644
--- a/html/user/show_user.php
+++ b/html/user/show_user.php
@@ -71,6 +71,9 @@ if ($format=="xml"){
} else {
// No data was found, generate new data for the cache and store it
$user = lookup_user_id($id);
+ if (!$user) {
+ error_page("No such user $id");
+ }
BoincForumPrefs::lookup($user);
$user = @get_other_projects($user);
$community_links = get_community_links_object($user);
@@ -97,6 +100,7 @@ if ($format=="xml"){
echo " | ";
start_table();
show_profile_link($user);
+ show_badges($user);
community_links($community_links, $logged_in_user);
end_table();
echo " | ";