diff --git a/html/ops/badge_assign_custom.php b/html/ops/badge_assign_custom.php
new file mode 100755
index 0000000000..8e12a67729
--- /dev/null
+++ b/html/ops/badge_assign_custom.php
@@ -0,0 +1,189 @@
+#!/usr/bin/env php
+.
+
+// Assign badges based on project total and per subproject credit.
+// This code is mostly generic.
+// You'll need to:
+// - define your subproject in project/project.inc
+// - use the option in config.xml
+// - supply your own project themed badge images
+// that have to follow a naming scheme (see below)
+// See: http://boinc.berkeley.edu/trac/wiki/PerAppCredit
+
+require_once("../inc/util_ops.inc");
+
+// use $sub_projects defined in project/project.inc
+// (this speeds up the assignment of badges)
+// "total" is a special sub project and should only be defined here
+//
+global $sub_projects;
+$badges_sub_projects = $sub_projects;
+$badges_sub_projects[] = array("name" => "project total", "short_name" => "total");
+
+// thresholds for the various badges
+// currently we use the same threshold for all badges (total and subproject)
+// minimum total credits for each level and corresponding names
+//
+$badge_levels = array(
+ 50000000, 100000000, 250000000, 500000000, 1000000000, 10000000000,
+ 250000000000, 500000000000
+);
+$badge_level_names = array(
+ "50M", "100M", "250M", "500M", "1B", "10B", "25B", "50B"
+);
+
+// images located in html/user/img/ for each badge level
+// the actual filename must have the subproject short name or "total" as prefix
+// e.g.: A_bronze.png is the first level of subproject A,
+// total_bronze.png is the first level of total credit (across all subprojects)
+//
+$badge_images = array(
+ "_bronze.png", "_silver.png", "_gold.png", "_amethyst.png",
+ "_turquoise.png", "_sapphire.png", "_ruby.png", "_emerald.png"
+);
+
+// consistency checks
+//
+$num_levels = count($badge_levels);
+if ($num_levels <> count($badge_level_names)) {
+ die("number of badge_levels is not equal to number of badge_level_names");
+}
+if ($num_levels <> count($badge_images)) {
+ die("number of badge_levels is not equal to number of badge_images");
+}
+
+// get the record for a badge (either total or subproject)
+// badge_name_prefix should be user or team
+// sub_project is an array with name and short_name as in $sub_projects
+//
+function get_badges(
+ $badge_name_prefix, $badge_level_names, $badge_images, $sub_project
+) {
+ $badges = array();
+ for ($i=0; $i=0; $i--) {
+ if ($item->total_credit >= $levels[$i]) {
+ assign_badge($is_user, $item, $badges[$i]);
+ unassign_badges($is_user, $item, $badges, $i);
+ return;
+ }
+ }
+ // no level could be assigned so remove them all
+ //
+ unassign_badges($is_user, $item, $badges, -1);
+}
+
+// decide which subproject badge to assign, if any.
+// Unassign other badges.
+//
+function assign_sub_badge($is_user, $item, $levels, $badges, $where_clause) {
+ if ($is_user) {
+ $sub_total = BoincCreditUser::sum('total', "where userid=".$item->id." and ($where_clause)");
+ } else {
+ $sub_total = BoincCreditTeam::sum('total', "where teamid=".$item->id." and ($where_clause)");
+ }
+ // count from highest to lowest level, so the user get's assigned the
+ // highest possible level and the lower levels get removed
+ //
+ for ($i=count($levels)-1; $i>=0; $i--) {
+ if ($sub_total >= $levels[$i]) {
+ assign_badge($is_user, $item, $badges[$i]);
+ unassign_badges($is_user, $item, $badges, $i);
+ return;
+ }
+ }
+ // no level could be assigned so remove them all
+ //
+ unassign_badges($is_user, $item, $badges, -1);
+}
+
+
+// Scan through all the users/teams, 1000 at a time,
+// and assign/unassign the badges (total and subproject)
+//
+function assign_all_badges(
+ $is_user, $badge_levels, $badge_level_names, $badge_images,
+ $subprojects_list
+) {
+ $kind = $is_user?"user":"team";
+
+ // get badges for all subprojects including total
+ //
+ foreach ($subprojects_list as $sp) {
+ $badges[$sp["short_name"]] = get_badges($kind, $badge_level_names, $badge_images, $sp);
+ }
+
+ $n = 0;
+ $maxid = $is_user?BoincUser::max("id"):BoincTeam::max("id");
+ while ($n <= $maxid) {
+ $m = $n + 1000;
+ if ($is_user) {
+ $items = BoincUser::enum_fields("id, total_credit", "id>=$n and id<$m and total_credit>0");
+ } else {
+ $items = BoincTeam::enum_fields("id, total_credit", "id>=$n and id<$m and total_credit>0");
+ }
+ // for every user/team
+ //
+ foreach ($items as $item) {
+ // for every subproject (incl. total)
+ //
+ foreach ($subprojects_list as $sp) {
+ if ($sp["short_name"] == "total") {
+ assign_tot_badge($is_user, $item, $badge_levels, $badges["total"]);
+ } else {
+ // appids come from project/project.inc
+ $where_clause = "appid in (". implode(',', $sp["appids"]) .")";
+ assign_sub_badge(
+ $is_user, $item, $badge_levels, $badges[$sp["short_name"]],
+ $where_clause
+ );
+ }
+ }
+ }
+ $n = $m;
+ }
+}
+
+// one pass through DB for users
+//
+assign_all_badges(
+ true, $badge_levels, $badge_level_names, $badge_images,
+ $badges_sub_projects
+);
+
+// one pass through DB for teams
+//
+assign_all_badges(
+ false, $badge_levels, $badge_level_names, $badge_images,
+ $badges_sub_projects
+);
+
+?>
diff --git a/html/project.sample/project.inc b/html/project.sample/project.inc
index 32429e6c7c..deacab6935 100644
--- a/html/project.sample/project.inc
+++ b/html/project.sample/project.inc
@@ -179,9 +179,15 @@ function show_app_credit_team($team, $app_name, $appids) {
);
}
+// a list of "sub-projects", used in the display of per-app credit and badges.
+// A subproject is:
+// - a set of 1 or more apps; an app can belong to at most 1 subproject.
+// - a name, shown on the web site
+// - a short name, used in badge names. Don't use "total".
+//
$sub_projects = array(
- array("name" => "Remote Test", "appids" => array(16)),
- array("name" => "Uppercase", "appids" => array(1, 25)),
+ array("name" => "Remote Test", "short_name" => "RT", "appids" => array(16)),
+ array("name" => "Uppercase", "short_name" => "UC", "appids" => array(1, 25)),
);
function project_user_credit($user){