- user web: add "friend" and "notification" features

- user web: code cleanup in Profile area
- GUI RPC: add missing "/" in auth2 RPC

svn path=/trunk/boinc/; revision=14394
This commit is contained in:
David Anderson 2007-12-18 20:28:08 +00:00
parent 963c26d0d1
commit 13baee4302
19 changed files with 1010 additions and 442 deletions

View File

@ -12304,3 +12304,27 @@ Rom 17 Dec 2007
win_build/installerv2/redist/Windows/x64/
boinccas.dll
boinccas95.dll
David 18 Dec 2007
- user web: add "friend" and "notification" features
- user web: code cleanup in Profile area
- GUI RPC: add missing "/" in auth2 RPC
db/
constraints.sql
schema.sql
html/
inc/
forum_db.inc
gallery.inc
profile.inc
team.inc
user.inc
ops/
db_update.php
user/
create_profile.php
friend.php (new)
show_user.php
lib/
gui_rpc_client.C

View File

@ -51,7 +51,9 @@ create table bolt_view (
primary key (id)
);
create table bolt_result (
-- represents the result of an exercise
--
create table bolt_exercise_result (
id integer not null auto_increment,
view_id integer not null,
score double not null,
@ -59,6 +61,14 @@ create table bolt_result (
primary key(id)
);
-- represents the result of an exercise set
--
create table bolt_exercise_set_result (
id integer not null auto_increment,
score double not null,
primary key(id)
);
create table bolt_refresh (
id integer not null auto_increment,
view_id integer not null,

View File

@ -117,3 +117,9 @@ alter table team_delta
alter table team_admin
add unique (teamid, userid);
alter table friend
add unique friend_u (user_src, user_dest);
alter table notify
add index notify_u (userid);

View File

@ -536,3 +536,31 @@ create table team_admin (
create_time integer not null,
rights integer not null
) type=MyISAM;
-- A friendship request.
-- The friendship exists if (x,y) and (y,x)
create table friend (
user_src integer not null,
-- initiator
user_dest integer not null,
-- target
message varchar(255) not null,
create_time integer not null,
reciprocated tinyint not null
-- whether the reciprocal exists
);
-- a notification of something, e.g.
-- a friend request or confirmation
-- a post in a subscribed thread
-- a personal message
-- These records are deleted when the user acts on them
create table notify (
id serial primary key,
userid integer not null,
-- destination of notification
create_time integer not null,
type integer not null,
opaque integer not null
-- some other ID, e.g. that of the thread, user or PM record
);

View File

@ -8,7 +8,7 @@ $bolt_ex_score = 0;
function bolt_exclusive_choice($choices) {
global $bolt_ex_mode; // input
global $bolt_ex_index; // input
global $bolt_ex_score; // output if SCORE
global $bolt_ex_score; // incremental output if SCORE
global $bolt_ex_response; // output if SCORE
switch ($bolt_ex_mode) {
@ -25,18 +25,26 @@ function bolt_exclusive_choice($choices) {
case BOLT_MODE_SCORE:
$right_ans = $choices[0];
shuffle($choices);
$response = $_GET["q_$bolt_ex_index"];
if ($choices[$response] == $right_ans) {
$bolt_ex_score = 1;
$key = "q_$bolt_ex_index";
if (array_key_exists($key, $_GET)) {
$response = $_GET[$key];
if ($choices[$response] == $right_ans) {
$bolt_ex_score += 1;
}
$bolt_ex_response = "$bolt_ex_index: $choices[$response]";
} else {
$bolt_ex_score = 0;
$bolt_ex_response = "";
}
$bolt_ex_response = "$bolt_ex_index: $choices[$response]";
break;
case BOLT_MODE_ANSWER:
$right_ans = $choices[0];
shuffle($choices);
$response = $_GET["q_$bolt_ex_index"];
$key = "q_$bolt_ex_index";
if (array_key_exists($key, $_GET)) {
$response = $_GET[$key];
} else {
$response = -1;
}
$i = 0;
start_table();
foreach ($choices as $choice) {
@ -55,7 +63,7 @@ function bolt_exclusive_choice($choices) {
function bolt_inclusive_choice($choices) {
global $bolt_ex_mode; // input
global $bolt_ex_index; // input
global $bolt_ex_score; // output if SCORE
global $bolt_ex_score; // incremental output if SCORE
global $bolt_ex_response; // output if SCORE
switch ($bolt_ex_mode) {
@ -65,7 +73,7 @@ function bolt_inclusive_choice($choices) {
start_table();
foreach ($choices as $choice) {
$c = $choice[0];
row2($c, "<input name=q_".$bolt_ex_index."_$i type=checkbox>");
row2("<input name=q_".$bolt_ex_index."_$i type=checkbox>", $c);
$i++;
}
end_table();
@ -81,22 +89,24 @@ function bolt_inclusive_choice($choices) {
$r = $choice[1];
$correct = ($r && $response) || (!$r && !$response);
if ($correct) $score += 1./$n;
$i++;
}
$bolt_ex_response = "$bolt_ex_index: $choices[$response]";
$bolt_ex_score += $score;
break;
case BOLT_MODE_ANSWER:
$i = 0;
$n = count($choices);
shuffle($choices);
start_table();
table_header("", "correct?", "your answer");
table_header("Choice", "Correct?", "Your answer");
foreach ($choices as $choice) {
$c = $choice[0];
$key = "q_".$bolt_ex_index."_$i";
$response = array_key_exists($key, $_GET);
$r = $choice[1];
$correct = ($r && $response) || (!$r && !$response);
table_row($c, $r?"X":"<br>", $response?"X":"<br>");
table_row($c, $r?"yes":"no", $response?"yes":"no");
$i++;
}
end_table();
@ -109,7 +119,7 @@ function bolt_image_rect($img, $rect) {
global $bolt_ex_mode; // input
global $bolt_ex_index; // input
global $bolt_ex_state; // output if SHOW, else input
global $bolt_ex_score; // output if SCORE
global $bolt_ex_score; // incremental output if SCORE
switch ($bolt_ex_mode) {
case BOLT_MODE_SHOW:

View File

@ -224,4 +224,47 @@ class BoincPostRating {
}
}
class BoincFriend {
static function insert($clause) {
$db = BoincDb::get();
return $db->insert('friend', $clause);
}
static function lookup($uid1, $uid2) {
$db = BoincDb::get();
return $db->lookup('friend', 'BoincFriend', "user_src=$uid1 and user_dest=$uid2");
}
function update($clause) {
$db = BoincDb::get();
return $db->update_aux('friend', "$clause where user_src=$this->user_src and user_dest=$this->user_dest");
}
static function enum($clause) {
$db = BoincDb::get();
return $db->enum('friend', 'BoincFriend', $clause);
}
}
class BoincNotify {
static function insert($clause) {
$db = BoincDb::get();
$ret = $db->insert('notify', $clause);
if (!$ret) return null;
return $db->insert_id();
}
static function enum($clause) {
$db = BoincDb::get();
return $db->enum('notify', 'BoincNotify', $clause);
}
static function lookup($userid, $type, $opaque) {
$db = BoincDb::get();
return $db->lookup('notify', 'BoincNotify', "userid=$userid and type=$type and opaque=$opaque");
}
function delete() {
$db = BoincDb::get();
return $db->delete($this, 'notify');
}
}
define ('NOTIFY_FRIEND_REQ', 1);
define ('NOTIFY_FRIEND_ACCEPT', 2);
?>

View File

@ -11,6 +11,60 @@ require_once("../inc/uotd.inc");
$alphabet = array('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9');
// Builds a summary table of user profiles.
//
// $members is an array of userIDs;
// $offset indicates which entry to begin the table with
// $numToDisplay indicates how many profiles to display in this table
// $cols indicates how many profile summaries should be written per row
// $descriptor is an optional file descriptor to write the table to.
function show_user_table($members, $offset, $numToDisplay, $cols) {
echo "<table class=bordered border=1 cellpadding=5>\n";
$rows = ceil($numToDisplay / $cols);
$count = $offset;
$numMembers = count($members);
for ($row = 0; $row < $rows; $row++) {
if ($count >= $numMembers) {
break;
}
echo "<tr>\n";
for ($col = 0; $col < $cols; $col++) {
if ($count < $numMembers) {
$profile = get_profile($members[$count]);
if (!$profile) {
$numMembers--;
continue;
}
echo "<td class=bordered width=7% height=64><center>";
$show_picture = $profile->has_picture;
if (profile_screening() && $profile->verification != 1) {
$show_picture = false;
}
if ($show_picture) {
echo "<a href=\"".URL_BASE."view_profile.php?userid={$members[$count]}\"><img src=\"".URL_BASE.IMAGE_URL."{$members[$count]}_sm.jpg\"></a>";
} else {
echo "&nbsp;";
}
echo "</center></td><td class=bordered width=33% height=64>\n", get_profile_summary($profile), "</td>";
$count++;
} else {
echo "<td width=7% height=64></td><td width=33% height=64></td>";
}
}
echo "</tr>\n";
}
echo "</table>\n";
}
// Generates a standard set of links between associated multi-page documents.
// All linked files must be of the form "$filename_<page number>.html".
@ -55,10 +109,6 @@ function build_picture_pages($width, $height) {
// TODO: Add support for a computer image gallery.
// TODO: Should we eliminate the has_picture flag? Doesn't really
// seem necessary when we're building static pages- could just use
// file_exists on the username...
// TODO: Standardize "Last modified" string to a function call (util.inc).
if (profile_screening()) {

View File

@ -7,7 +7,7 @@ require_once("../inc/cache.inc");
require_once("../inc/user.inc");
require_once("../inc/translation.inc");
require_once("../inc/text_transform.inc");
require_once("../inc/db_forum.inc");
require_once("../inc/forum.inc");
require_once("../inc/recaptchalib.php");
define('SMALL_IMG_WIDTH', 64);
@ -26,120 +26,34 @@ function profile_screening() {
return parse_bool($config, "profile_screening");
}
// output a select form item with the given name,
// from a list of newline-delineated items from the text file.
// If $selection is provided, and if it matches one of the entries in the file,
// it will be selected by default.
//
function show_combo_box($name, $filename, $selection=null) {
if (!file_exists($filename)) {
echo "ERROR: $filename does not exist! Cannot create combo box.<br>";
exit();
}
echo "<select name=\"$name\">\n";
$file = fopen($filename, "r");
while ($line = trim(fgets($file, 1024))) {
if ($line == $selection) {
echo "<option SELECTED value=\"$line\">$line\n";
} else {
echo "<option value=\"$line\">$line\n";
}
}
echo "</select>\n";
fclose($file);
}
function get_profile($userid) {
return BoincProfile::lookup("userid = $userid");
}
function show_profile_creation_page($user) {
$config = get_config();
$min_credit = parse_config($config, "<profile_min_credit>");
if ($min_credit && $user->expavg_credit < $min_credit) {
error_page(
"To prevent spam, an average credit of $min_credit or greater is required to create or edit a profile. We apologize for this inconvenience."
);
}
// TODO: use the following functions instead of hardwired crap everywhere
// If the user already has a profile,
// fill in the fields with their current values.
//
$profile = get_profile($user->id);
if (post_str("submit", true)) {
$privatekey = parse_config($config, "<recaptcha_private_key>");
if ($privatekey) {
$resp = recaptcha_check_answer($privatekey, $_SERVER["REMOTE_ADDR"],
$_POST["recaptcha_challenge_field"], $_POST["recaptcha_response_field"]
);
if (!$resp->is_valid) {
error_page("The reCAPTCHA wasn't entered correctly. Go back and try it again.<br>".
"(reCAPTCHA said: " . $resp->error . ")"
);
}
}
process_create_profile($user, $profile);
exit();
}
if ($profile) {
page_head("Edit your profile");
} else {
page_head("Create a profile");
}
echo "
<form action=", $_SERVER['PHP_SELF'], " method=\"POST\", ENCTYPE=\"multipart/form-data\">
";
start_table_noborder();
show_description();
show_questions($profile);
show_picture_option($profile);
show_submit();
end_table();
echo "</form>";
page_tail();
function profile_image_path($userid) {
return IMAGE_PATH.$userid.'.jpg';
}
function show_description() {
echo "
<p>
Your <b>profile</b> lets you share your opinions and background
with the ".PROJECT." community.
<p>
";
function profile_thumb_path($userid) {
return IMAGE_PATH.$userid.'_sm.jpg';
}
function show_questions($profile) {
$response1 = "";
$response2 = "";
if (isset($profile->response1)) {
$response1 = stripslashes($profile->response1);
}
if (isset($profile->response2)) {
$response2 = stripslashes($profile->response2);
}
row1(show_profile_heading1());
rowify(show_profile_question1().html_info());
rowify("<br>");
show_textarea("response1", $response1);
rowify("<br>");
row1( show_profile_heading2());
rowify( show_profile_question2().html_info());
rowify("<br>");
show_textarea("response2", $response2);
rowify("<br>");
show_language_selection($profile);
rowify("<br>");
function profile_image_url($userid) {
return URL_BASE.IMAGE_URL.$userid.'.jpg';
}
function show_textarea($name, $text) {
rowify("<textarea name=\"$name\" cols=80 rows=20>" . $text . "</textarea>");
function profile_thumb_url($userid) {
return URL_BASE.IMAGE_URL.$userid.'_sm.jpg';
}
function profile_user_thumb_url($user) {
if (!$user->has_profile) return null;
$profile = BoincProfile::lookup("userid=$user->id");
if (!$profile->has_picture) return null;
if (profile_screening() && $profile->verification!=1) return null;
return profile_thumb_url($user->id);
}
// When passed profile->verification, this function is used to tell the
@ -165,211 +79,20 @@ function offensive_profile_warning($verify_flag) {
return "";
}
function show_picture_option($profile) {
row1("Picture");
$warning = "";
if (profile_screening() && $profile->has_picture) {
$warning = offensive_profile_warning($profile->verification);
}
if (($profile) && ($profile->has_picture)) {
echo "
<tr><td colspan=2>
<table border=0 cellpadding=5
<tr>
<td valign=top><a href=\"" . IMAGE_URL . $profile->userid . '.jpg' . "\"><img src=\"" . IMAGE_URL . $profile->userid . '_sm.jpg' . "\"></a>
</td>
<td valign=top> $warning Your profile picture is shown at left.
<p>
To replace it,
click the \"Browse\" button and select a JPEG or PNG file (50KB or less).
<input name=picture type=file><br>
<p>
To remove it from your profile, check this box:
<input type=checkbox name=delete_pic>
<p>
</td></tr>";
rowify("<br>");
end_table();
echo "</td></tr>";
} else {
rowify("
If you would like include a picture with your profile,
click the \"Browse\" button and select a JPEG or PNG file.
Please select images of 50KB or less.
<p>
<input name=picture type=file>
");
rowify("<br>");
}
}
function show_language_selection($profile) {
row1("Language");
echo "<tr><td>
<p>
Select the language in which your profile is written:
<p>
";
if (isset($profile->language)) {
show_combo_box("language", LANGUAGE_FILE, $profile->language);
} else {
show_combo_box("language", LANGUAGE_FILE, "English");
}
echo "</td></tr>\n";
}
function show_submit() {
row1("Submit profile");
echo "<script>var RecaptchaOptions = { theme : 'white' };</script>"; // White looks better :)
$config = get_config();
$publickey = parse_config($config, "<recaptcha_public_key>");
if ($publickey) {
table_row("To protect project's webpages from spam, we ask you to type in two words shown in the image:<br>\n".
recaptcha_get_html($publickey));
}
table_row("<p><input type=\"submit\" value=\"Create/edit profile\" name=\"submit\">");
}
// If the user with id = $userid has uploaded a picture his/herself,
// delete it and its thumbnail.
//
function delete_user_pictures($userid) {
$filename1 = IMAGE_PATH . $userid . '.jpg';
$filename2 = IMAGE_PATH . $userid . '_sm.jpg';
if (file_exists($filename1)) {
unlink($filename1);
$path = profile_image_path($userid);
if (file_exists($path)) {
unlink($path);
}
if (file_exists($filename2)) {
unlink($filename2);
$path = profile_thumb_path($userid);
if (file_exists($path)) {
unlink($path);
}
}
// $profile is null if user doesn't already have a profile.
// Don't assign to $profile->x if this is the case.
//
function process_create_profile($user, $profile) {
$response1 = $_POST['response1'];
$response2 = $_POST['response2'];
$language = $_POST['language'];
if (isset($_POST['delete_pic'])) {
$delete_pic = $_POST['delete_pic'];
} else {
$delete_pic = "off";
}
if (strlen($response1)==0 &&
strlen($response2)==0 &&
$delete_pic != "on" &&
!is_uploaded_file($_FILES['picture']['tmp_name'])
) {
error_page("Your profile submission was empty.");
exit();
}
if ($delete_pic == "on") {
delete_user_pictures($profile->userid);
$profile->has_picture = false;
$profile->verification = 0;
}
$profile ? $hasPicture = $profile->has_picture: $hasPicture = false;
if (is_uploaded_file($_FILES['picture']['tmp_name'])) {
$hasPicture = true;
if ($profile) $profile->verification = 0;
// echo "<br>Name: " . $_FILES['picture']['name'];
// echo "<br>Type: " . $_FILES['picture']['type'];
// echo "<br>Size: " . $_FILES['picture']['size'];
// echo "<br>Temp name: " . $_FILES['picture']['tmp_name'];
$images = getImages($_FILES['picture']['tmp_name']);
// Write the original image file to disk.
// TODO: define a constant for image quality.
ImageJPEG($images[0], IMAGE_PATH . $user->id . '.jpg');
ImageJPEG($images[1], IMAGE_PATH . $user->id . '_sm.jpg');
}
$response1 = sanitize_html($response1);
$response2 = sanitize_html($response2);
if ($profile) {
$query = " response1 = '".boinc_real_escape_string($response1)."',"
." response2 = '".boinc_real_escape_string($response2)."',"
." language = '".boinc_real_escape_string($language)."',"
." has_picture = '$hasPicture',"
." verification = '$profile->verification'"
." WHERE userid = '$user->id'";
$result = BoincProfile::update_aux($query);
if (!$result) {
error_page("Couldn't update profile: database error");
}
} else {
$query = 'SET '
." userid = '$user->id',"
." language = '".boinc_real_escape_string($language)."',"
." response1 = '".boinc_real_escape_string($response1)."',"
." response2 = '".boinc_real_escape_string($response2)."',"
." has_picture = '$hasPicture',"
." verification=0";
$result = BoincProfile::insert($query);
if (!$result) {
error_page("Couldn't create profile: database error");
}
$user->update("has_profile=1");
}
show_result_page($user);
}
// Returns an array containing:
// [0]: The original image refered to by $fileName if its dimensions are
// less than MAX_IMG_WIDTH x MAX_IMG_HEIGHT, or a version scaled to
// those dimensions if it was too large.
// [1]: A scaled version of the above.
function getImages($fileName) {
$size = getImageSize($fileName);
// Determine if the filetype uploaded is supported.
// TODO: Change these to constants.
switch($size[2]) {
case '2': // JPEG
$image = imageCreateFromJPEG($fileName);
break;
case '3': // PNG
$image = imageCreateFromPNG($fileName);
break;
default:
error_page("The format of your uploaded image is not supported by our system.");
}
$width = $size[0];
$height = $size[1];
$smallImage = scale_image($image, $width, $height, SMALL_IMG_WIDTH, SMALL_IMG_HEIGHT);
if ($width > MAX_IMG_WIDTH || $height > MAX_IMG_HEIGHT) {
$image = scale_image($image, $width, $height, MAX_IMG_WIDTH, MAX_IMG_HEIGHT);
}
/*
echo "<br><br>Image type: $size[2]";
echo "<br>Original width: $width";
echo "<br>Original height: $height";
echo "<br>Scalar: $scalar";
echo "<br>Dest width: " . ($width / $scalar);
echo "<br>Dest height: " . ($height / $scalar);
echo "<br>Horizontal offset: $horiz_offset";
echo "<br>Vertical offset: $vert_offset";
echo "<br><br><a href=\"images/user_profile/test.jpg\">View result</a>";
*/
return array($image, $smallImage);
}
function scale_image(
$image, $origWidth, $origHeight, $targetWidth, $targetHeight
) {
@ -414,72 +137,6 @@ function scale_image(
return $newImage;
}
function show_result_page($user) {
page_head("Profile Saved");
echo "
<h2>Congratulations!</h2><p>
Your profile was successfully entered into our database.<br><br>
<a href=view_profile.php?userid=$user->id>View your profile</a><br>
";
page_tail();
}
// Builds a summary table of user profiles.
//
// $members is an array of userIDs;
// $offset indicates which entry to begin the table with
// $numToDisplay indicates how many profiles to display in this table
// $cols indicates how many profile summaries should be written per row
// $descriptor is an optional file descriptor to write the table to.
function show_user_table($members, $offset, $numToDisplay, $cols) {
echo "<table class=bordered border=1 cellpadding=5>\n";
$rows = ceil($numToDisplay / $cols);
$count = $offset;
$numMembers = count($members);
for ($row = 0; $row < $rows; $row++) {
if ($count >= $numMembers) {
break;
}
echo "<tr>\n";
for ($col = 0; $col < $cols; $col++) {
if ($count < $numMembers) {
$profile = get_profile($members[$count]);
if (!$profile) {
$numMembers--;
continue;
}
echo "<td class=bordered width=7% height=64><center>";
$show_picture = $profile->has_picture;
if (profile_screening() && $profile->verification != 1) {
$show_picture = false;
}
if ($show_picture) {
echo "<a href=\"".URL_BASE."view_profile.php?userid={$members[$count]}\"><img src=\"".URL_BASE.IMAGE_URL."{$members[$count]}_sm.jpg\"></a>";
} else {
echo "&nbsp;";
}
echo "</center></td><td class=bordered width=33% height=64>\n", get_profile_summary($profile), "</td>";
$count++;
} else {
echo "<td width=7% height=64></td><td width=33% height=64></td>";
}
}
echo "</tr>\n";
}
echo "</table>\n";
}
// Generates a string containing:
// 1) the name of the user with ID == $userid,
// with a link to a view of their profile
@ -507,8 +164,10 @@ function get_profile_summary($profile) {
}
// Displays a user's profile (if they have one);
// $screen_mode is set if we're in the administrative profile-screening page,
// in which case we show everything
function show_profile($userid, $verify_mode = FALSE) {
function show_profile($userid, $screen_mode = false) {
$user = get_user_from_id($userid);
if (!$user) {
@ -522,7 +181,7 @@ function show_profile($userid, $verify_mode = FALSE) {
if (!$profile) {
error_page("No user profile exists for that user ID.");
}
if (!$verify_mode) {
if (!$screen_mode) {
$logged_in_user = get_logged_in_user(false);
if (!$logged_in_user || ($user->id != $logged_in_user->id)) {
$caching = true;
@ -532,7 +191,7 @@ function show_profile($userid, $verify_mode = FALSE) {
}
$can_edit = isset($logged_in_user) && $logged_in_user && $user->id == $logged_in_user->id;
if (!$verify_mode) {
if (!$screen_mode) {
page_head("Profile: ".$user->name);
}
@ -542,19 +201,18 @@ function show_profile($userid, $verify_mode = FALSE) {
row1("<a href=create_profile.php>Edit your profile</a>");
}
// If doing screening, only show picture in certain situations
// If screening is enabled, only show picture in certain situations
//
$show_picture = $profile->has_picture;
if (profile_screening()) {
if (!$verify_mode && !$can_edit && $profile->verification!=1) {
if (!$screen_mode && !$can_edit && $profile->verification!=1) {
$show_picture = false;
}
}
if ($show_picture) {
echo "
<tr><td colspan=\"2\" align=\"center\">
<img vspace=\"6\" hspace=\"9\" src=\"".URL_BASE.IMAGE_URL.$user->id.".jpg\">
<img vspace=\"6\" hspace=\"9\" src=\"".profile_image_url($user->id)."\">
</td></tr>
";
}
@ -571,9 +229,9 @@ function show_profile($userid, $verify_mode = FALSE) {
// Setup text output options based on logged in user forum settings
//
if (!$verify_mode) {
if (!$screen_mode) {
$logged_in_user = get_logged_in_user(false);
$logged_in_user = getForumPreferences($logged_in_user);
$logged_in_user = BoincForumPrefs::lookup($logged_in_user);
$options = get_transform_settings_from_user($logged_in_user);
}
@ -582,7 +240,7 @@ function show_profile($userid, $verify_mode = FALSE) {
row1(show_profile_heading2());
row1(output_transform($profile->response2,$options), 2, "foobar");
if (!$can_edit and !$verify_mode) {
if (!$can_edit and !$screen_mode) {
row1("Your feedback on this profile");
row2(
"Recommend this profile for User of the Day:",
@ -595,7 +253,7 @@ function show_profile($userid, $verify_mode = FALSE) {
}
end_table();
if (!$verify_mode) {
if (!$screen_mode) {
page_tail();
} else {
echo "<hr>";

View File

@ -269,10 +269,12 @@ function new_member_list($teamid) {
$new_members = array();
$yesterday = time() - 86400;
$deltas = BoincTeamDelta::enum("teamid=$teamid and timestamp>$yesterday and joining=1 group by userid");
foreach ($deltas as $delta) {
$u = BoincUser::lookup_id($delta->userid);
if ($u->teamid == $teamid) {
$new_members[] = $u; // they might have later quit
if (count($deltas)) {
foreach ($deltas as $delta) {
$u = BoincUser::lookup_id($delta->userid);
if ($u->teamid == $teamid) {
$new_members[] = $u; // they might have later quit
}
}
}
return $new_members;

View File

@ -147,6 +147,25 @@ function show_user_stats_private($user) {
row2(tra("Stats on your cell phone"), URL_BASE."userw.php?id=$user->id");
}
function notify_description($notify) {
switch ($notify->type) {
case NOTIFY_FRIEND_REQ:
$user = BoincUser::lookup_id($notify->opaque);
return "
<a href=friend.php?action=query&userid=$notify->opaque>Friend request from $user->name</a>
<br>
";
break;
case NOTIFY_FRIEND_ACCEPT:
$user = BoincUser::lookup_id($notify->opaque);
return "
<a href=friend.php?action=accepted&userid=$notify->opaque>$user->name confirmed as friend</a>
<br>
";
break;
}
}
// show static user info (private)
//
function show_user_info_private($user) {
@ -199,6 +218,25 @@ function show_user_info_private($user) {
row2(tra("Private messages"), pm_notification($user).pm_email_remind($user));
$notifies = BoincNotify::enum("userid=$user->id");
if (count($notifies)) {
$x = "";
foreach ($notifies as $notify) {
$x .= notify_description($notify);
}
row2("Notifications", $x);
}
$friends = BoincFriend::enum("user_src=$user->id and reciprocated=1");
if (count($friends)) {
$x = "";
foreach($friends as $friend) {
$fuser = BoincUser::lookup_id($friend->user_dest);
$x .= user_links($fuser);
}
row2("Friends", $x);
}
if ($user->teamid) {
$team = lookup_team($user->teamid);
$x = "<a href=\"team_display.php?teamid=$team->id\">$team->name</a>
@ -234,6 +272,7 @@ function show_user_info_private($user) {
// show summary of dynamic and static info (public)
//
function show_user_summary_public($user) {
global $g_logged_in_user;
row2(PROJECT." member since", date_str($user->create_time));
row2("Country", $user->country);
if (strlen($user->url)) {
@ -264,7 +303,23 @@ function show_user_summary_public($user) {
}
}
row2("Contact", "<a href=\"pm.php?action=new&amp;userid=".$user->id."\">Send private message</a>");
$friends = BoincFriend::enum("user_src=$user->id and reciprocated=1");
if (count($friends)) {
$x = "";
foreach($friends as $friend) {
$fuser = BoincUser::lookup_id($friend->user_dest);
$x .= user_links($fuser);
}
row2("Friends", $x);
}
if ($g_logged_in_user && $g_logged_in_user->id != $user->id) {
row2("Contact", "<a href=\"pm.php?action=new&userid=".$user->id."\">Send private message</a>");
$friend = BoincFriend::lookup($g_logged_in_user->id, $user->id);
if (!$friend) {
row2("Community", "<a href=friend.php?action=add&userid=".$user->id.">Add as friend</a>");
}
}
}
function show_profile_link($user) {

40
html/ops/bolt_refresh.php Normal file
View File

@ -0,0 +1,40 @@
<?php
// send reminder emails for Bolt refresh.
// Run this script at most once a day
// (to avoid multiple emails)
require_once("../inc/bolt_db.inc");
function notify_user($user) {
$body = "
You are due for refresh on the following units from
";
foreach($user->refresh as $r) {
echo "
}
}
function notify_users() {
$now = time();
$rs = BoltRefresh::enum("due_time < $now");
$users = array();
foreach($rs as $r) {
$view = BoltView::lookup_id($r->view_id);
$user_id = $view->user_id;
if (!key_exists($user_id)) {
$user = BoincUser::lookup_id($user_id);
BoltUser::lookup($user);
$user->refresh = array();
$users[$user_id] = $user;
}
$users[$user_id]->refresh[] = $r;
}
foreach ($users as $user) {
notify_user($user);
}
}
?>

View File

@ -509,6 +509,31 @@ function update_11_20_2007() {
do_query("alter table team add fulltext index team_name(name)");
}
function update_12_18_2007() {
do_query("create table friend (
user_src integer not null,
user_dest integer not null,
message varchar(255) not null,
create_time integer not null,
reciprocated tinyint not null
)
");
do_query("create table notify (
id serial primary key,
userid integer not null,
create_time integer not null,
type integer not null,
opaque integer not null
)
");
do_query("alter table friend
add unique friend_u (user_src, user_dest)
");
do_query("alter table notify
add index notify_u (userid)
");
}
// modify the following to call the function you want.
// Make sure you do all needed functions, in order.
// (Look at your DB structure using "explain" queries to see

View File

@ -10,20 +10,28 @@ $user = get_logged_in_user(true);
$courses = BoltCourse::enum();
start_table();
table_header(
"Course<br><span class=note>Click to start, resume, or review</span>
", "Status"
"Course", "Status"
);
foreach ($courses as $course) {
$e = $user?BoltEnrollment::lookup($user->id, $course->id):null;
$start = date_str($e->create_time);
$ago = time_diff(time() - $e->last_view);
if ($e) {
$pct = number_format($e->fraction_done*100, 0);
$status = "Started $start; last visit $ago ago; $pct% done";
$start = date_str($e->create_time);
$view = BoltView::lookup_id($e->last_view_id);
$ago = time_diff(time() - $view->start_time);
$pct = number_format($view->fraction_done*100, 0);
$status = "Started $start
<br>Last visit: $ago ago
<br>$pct% done
<br><a href=bolt_sched.php?course_id=$course->id>Resume</a>
<br><a href=bolt_course.php?course_id=$course->id>History</a>
<br><a href=bolt_sched.php?course_id=$course->id&action=start>Restart</a>
";
} else {
$status = "Not started";
$status = "
<a href=bolt_sched.php?course_id=$course->id&action=start>Start</a>
";
}
row2("<a href=bolt_sched.php?course_id=$course->id&action=start>$course->name</a>
row2("<b>$course->name</b>
<br><span class=note>$course->description</span>",
$status
);

View File

@ -9,7 +9,7 @@ function mode_name($mode) {
case BOLT_MODE_LESSON: return "lesson";
case BOLT_MODE_SHOW: return "exercise";
case BOLT_MODE_ANSWER: return "exercise answer";
default: return "unknown";
default: return "unknown: $mode";
}
}
@ -17,9 +17,10 @@ function action_name($action) {
switch ($action) {
case BOLT_ACTION_NONE: return "None";
case BOLT_ACTION_NEXT: return "Next";
case BOLT_ACTION_PREV: return "Previous";
case BOLT_ACTION_SUBMIT: return "Submit";
case BOLT_ACTION_QUESTION: return "Question";
default: return "unknown";
default: return "unknown: $action";
}
}

View File

@ -27,6 +27,7 @@ if (!$course) {
}
function update_info() {
global $user;
$sex = get_int('sex');
$birth_year = get_int('birth_year');
$user->bolt->update("sex=$sex, birth_year=$birth_year");
@ -38,31 +39,45 @@ $course_doc = require_once($course->doc_file);
function finalize_view($user, $view_id, $action) {
if (!$view_id) return null;
$view = BoltView::lookup_id($view_id);
if ($view && $view->user_id == $user->id && !$view->end_time) {
if (!$view) {
error_page("no view");
}
if ($view->user_id != $user->id) {
error_page("wrong user");
}
if (!$view->end_time) {
$now = time();
$view->update("end_time=$now, action=$action");
return $view;
}
return null;
return $view;
}
function default_mode($item) {
return $item->is_exercise()?BOLT_MODE_SHOW:BOLT_MODE_LESSON;
}
function create_view($user, $course, $iter, $mode) {
function create_view($user, $course, $iter, $mode, $prev_view_id) {
$now = time();
$item = $iter->item;
$state = $iter->encode_state();
return BoltView::insert("(user_id, course_id, item_name, start_time, mode, state, fraction_done) values ($user->id, $course->id, '$item->name', $now, $mode, '$state', $iter->frac_done)");
return BoltView::insert("(user_id, course_id, item_name, start_time, mode, state, fraction_done, prev_view_id) values ($user->id, $course->id, '$item->name', $now, $mode, '$state', $iter->frac_done, $prev_view_id)");
}
function show_item($iter, $user, $course, $view_id, $mode) {
function show_item($iter, $user, $course, $view_id, $prev_view_id, $mode) {
global $bolt_ex_mode;
global $bolt_ex_index;
global $bolt_ex_score;
$item = $iter->item;
if ($item->is_exercise()) {
page_head(null);
if ($item) {
$title = $item->title;
} else {
$title = "Course completed";
}
if (function_exists('bolt_header')) bolt_header($title);
if ($item && $item->is_exercise()) {
$bolt_ex_mode = $mode;
$bolt_ex_index = 0;
switch ($mode) {
@ -75,22 +90,48 @@ function show_item($iter, $user, $course, $view_id, $mode) {
";
srand($view_id);
require($item->filename);
echo "<p><input type=submit value=OK>";
if (function_exists('bolt_divide')) bolt_divide();
$next = "<input type=submit value=OK></form>";
break;
case BOLT_MODE_ANSWER:
require($item->filename);
echo "<p><a href=bolt_sched.php?course_id=$course->id&action=next&view_id=$view_id>Next</a>";
if (function_exists('bolt_divide')) bolt_divide();
$next = "<a href=bolt_sched.php?course_id=$course->id&action=next&view_id=$view_id>Next >></a>";
$score_pct = number_format($bolt_ex_score*100);
echo "Score: $score_pct%";
break;
}
} else {
} else if ($item) {
require_once($item->filename);
echo "<p><a href=bolt_sched.php?course_id=$course->id&action=next&view_id=$view_id>Next</a>";
if (function_exists('bolt_divide')) bolt_divide();
$next = "<a href=bolt_sched.php?course_id=$course->id&action=next&view_id=$view_id>Next >></a>";
} else {
echo "Congratulation - you have completed this course.";
$next = "";
}
echo "<p>Fraction done: $iter->frac_done
<p><a href=bolt_course.php?course_id=$course->id>Course history</a>
<p><a href=bolt_sched.php?course_id=$course->id&action=prev&view_id=$view_id>Prev</a>
if ($prev_view_id) {
$prev = "<a href=bolt_sched.php?course_id=$course->id&action=prev&view_id=$view_id><< Prev</a>";
} else {
$prev = "";
}
echo "
<p>
<center>
<table width=60%><tr>
<td width=33% align=left>$prev</td>
<td width=33% align=center><a href=bolt.php>Up</a></td>
<td width=33% align=right>$next</td>
</table>
</center>
";
if (function_exists('bolt_footer')) bolt_footer();
$e = new BoltEnrollment();
$e->user_id = $user->id;
$e->course_id = $course->id;
$e->update("last_view_id=$view_id");
}
function start_course($user, $course, $course_doc) {
@ -100,9 +141,9 @@ function start_course($user, $course, $course_doc) {
$now = time();
$mode = default_mode($iter->item);
$view_id = create_view($user, $course, $iter, $mode);
$view_id = create_view($user, $course, $iter, $mode, 0);
BoltEnrollment::insert("(create_time, user_id, course_id, last_view_id) values ($now, $user->id, $course->id, $view_id)");
show_item($iter, $user, $course, $view_id, $mode);
show_item($iter, $user, $course, $view_id, 0, $mode);
}
$e = BoltEnrollment::lookup($user->id, $course_id);
@ -132,7 +173,20 @@ case 'update_info':
update_info();
start_course($user, $course, $course_doc);
case 'prev':
$view = finalize_view($user, $view_id, BOLT_ACTION_NEXT);
$view = finalize_view($user, $view_id, BOLT_ACTION_PREV);
if ($view->prev_view_id) {
$view = BoltView::lookup_id($view->prev_view_id);
$iter = new BoltIter($course_doc);
$iter->decode_state($view->state);
$iter->at();
$mode = default_mode($iter->item);
$view_id = create_view(
$user, $course, $iter, $mode, $view->prev_view_id
);
show_item($iter, $user, $course, $view_id, $view->prev_view_id, $mode);
} else {
error_page("At start of course");
}
break;
case 'next': // "next" button in lesson or exercise answer page
$view = finalize_view($user, $view_id, BOLT_ACTION_NEXT);
@ -141,16 +195,14 @@ case 'next': // "next" button in lesson or exercise answer page
$iter->decode_state($view->state);
$iter->next();
if (!$iter->item) {
page_head("Done with course");
echo "All done!";
page_tail();
exit();
if ($iter->item) {
$state = $iter->encode_state();
$mode = default_mode($iter->item);
$view_id = create_view($user, $course, $iter, $mode, $view->id);
show_item($iter, $user, $course, $view_id, $view->id, $mode);
} else {
show_item($iter, $user, $course, 0, $view->id, 0);
}
$state = $iter->encode_state();
$mode = default_mode($iter->item);
$view_id = create_view($user, $course, $iter, $mode);
show_item($iter, $user, $course, $view_id, $mode);
break;
case 'answer': // submit answer in exercise
$view = finalize_view($user, $view_id, BOLT_ACTION_SUBMIT);
@ -175,22 +227,29 @@ case 'answer': // submit answer in exercise
ob_end_clean();
$bolt_ex_response = BoltDb::escape_string($bolt_ex_response);
$bolt_ex_score /= $bolt_ex_index;
$result_id = BoltResult::insert(
"(view_id, score, response)
values ($view->id, $bolt_ex_score, '$bolt_ex_response')"
);
$view->update("result_id=$result_id");
srand($view_id);
$view_id = create_view($user, $course, $iter, BOLT_MODE_ANSWER);
show_item($iter, $user, $course, $view_id, BOLT_MODE_ANSWER);
$view_id = create_view($user, $course, $iter, BOLT_MODE_ANSWER, $view->id);
show_item($iter, $user, $course, $view_id, $view->id, BOLT_MODE_ANSWER);
break;
default:
$view = $e?BoltView::lookup_id($e->last_view_id):null;
if (!$view) {
start_course($user, $course, $course_doc);
break;
}
$iter = new BoltIter($course_doc);
$iter->decode_state($view->state);
$iter->at();
$mode = default_mode($iter->item);
$view_id = create_view($user, $course, $iter, $mode);
show_item($iter, $user, $course, $view_id, $mode);
$view_id = create_view($user, $course, $iter, $mode, $view->id);
show_item($iter, $user, $course, $view_id, $view->id, $mode);
break;
}

View File

@ -1,7 +1,320 @@
<?php
// TODO: the following is organized in a funky way. Clean it up
require_once("../inc/profile.inc");
// output a select form item with the given name,
// from a list of newline-delineated items from the text file.
// If $selection is provided, and if it matches one of the entries in the file,
// it will be selected by default.
//
function show_combo_box($name, $filename, $selection=null) {
if (!file_exists($filename)) {
echo "ERROR: $filename does not exist! Cannot create combo box.<br>";
exit();
}
echo "<select name=\"$name\">\n";
$file = fopen($filename, "r");
while ($line = trim(fgets($file, 1024))) {
if ($line == $selection) {
echo "<option SELECTED value=\"$line\">$line\n";
} else {
echo "<option value=\"$line\">$line\n";
}
}
echo "</select>\n";
fclose($file);
}
function show_picture_option($profile) {
row1("Picture");
$warning = "";
if (profile_screening() && $profile->has_picture) {
$warning = offensive_profile_warning($profile->verification);
}
if (($profile) && ($profile->has_picture)) {
echo "
<tr><td colspan=2>
<table border=0 cellpadding=5
<tr>
<td valign=top><a href=\"" . IMAGE_URL . $profile->userid . '.jpg' . "\"><img src=\"" . IMAGE_URL . $profile->userid . '_sm.jpg' . "\"></a>
</td>
<td valign=top> $warning Your profile picture is shown at left.
<p>
To replace it,
click the \"Browse\" button and select a JPEG or PNG file (50KB or less).
<input name=picture type=file><br>
<p>
To remove it from your profile, check this box:
<input type=checkbox name=delete_pic>
<p>
</td></tr>";
rowify("<br>");
end_table();
echo "</td></tr>";
} else {
rowify("
If you would like include a picture with your profile,
click the \"Browse\" button and select a JPEG or PNG file.
Please select images of 50KB or less.
<p>
<input name=picture type=file>
");
rowify("<br>");
}
}
function show_language_selection($profile) {
row1("Language");
echo "<tr><td>
<p>
Select the language in which your profile is written:
<p>
";
if (isset($profile->language)) {
show_combo_box("language", LANGUAGE_FILE, $profile->language);
} else {
show_combo_box("language", LANGUAGE_FILE, "English");
}
echo "</td></tr>\n";
}
function show_submit() {
row1("Submit profile");
echo "<script>var RecaptchaOptions = { theme : 'white' };</script>"; // White looks better :)
$config = get_config();
$publickey = parse_config($config, "<recaptcha_public_key>");
if ($publickey) {
table_row("To protect project's webpages from spam, we ask you to type in two words shown in the image:<br>\n".
recaptcha_get_html($publickey));
}
table_row("<p><input type=\"submit\" value=\"Create/edit profile\" name=\"submit\">");
}
// Returns an array containing:
// [0]: The original image refered to by $fileName if its dimensions are
// less than MAX_IMG_WIDTH x MAX_IMG_HEIGHT, or a version scaled to
// those dimensions if it was too large.
// [1]: A scaled version of the above.
function getImages($fileName) {
$size = getImageSize($fileName);
// Determine if the filetype uploaded is supported.
// TODO: Change these to constants.
switch($size[2]) {
case '2': // JPEG
$image = imageCreateFromJPEG($fileName);
break;
case '3': // PNG
$image = imageCreateFromPNG($fileName);
break;
default:
error_page("The format of your uploaded image is not supported.");
}
$width = $size[0];
$height = $size[1];
$smallImage = scale_image($image, $width, $height, SMALL_IMG_WIDTH, SMALL_IMG_HEIGHT);
if ($width > MAX_IMG_WIDTH || $height > MAX_IMG_HEIGHT) {
$image = scale_image($image, $width, $height, MAX_IMG_WIDTH, MAX_IMG_HEIGHT);
}
/*
echo "<br><br>Image type: $size[2]";
echo "<br>Original width: $width";
echo "<br>Original height: $height";
echo "<br>Scalar: $scalar";
echo "<br>Dest width: " . ($width / $scalar);
echo "<br>Dest height: " . ($height / $scalar);
echo "<br>Horizontal offset: $horiz_offset";
echo "<br>Vertical offset: $vert_offset";
echo "<br><br><a href=\"images/user_profile/test.jpg\">View result</a>";
*/
return array($image, $smallImage);
}
function show_description() {
echo "
<p>
Your <b>profile</b> lets you share your opinions and background
with the ".PROJECT." community.
<p>
";
}
function show_questions($profile) {
$response1 = "";
$response2 = "";
if (isset($profile->response1)) {
$response1 = stripslashes($profile->response1);
}
if (isset($profile->response2)) {
$response2 = stripslashes($profile->response2);
}
row1(show_profile_heading1());
rowify(show_profile_question1().html_info());
rowify("<br>");
show_textarea("response1", $response1);
rowify("<br>");
row1( show_profile_heading2());
rowify( show_profile_question2().html_info());
rowify("<br>");
show_textarea("response2", $response2);
rowify("<br>");
show_language_selection($profile);
rowify("<br>");
}
function show_textarea($name, $text) {
rowify("<textarea name=\"$name\" cols=80 rows=20>" . $text . "</textarea>");
}
// $profile is null if user doesn't already have a profile.
// Don't assign to $profile->x if this is the case.
//
function process_create_profile($user, $profile) {
$response1 = $_POST['response1'];
$response2 = $_POST['response2'];
$language = $_POST['language'];
if (isset($_POST['delete_pic'])) {
$delete_pic = $_POST['delete_pic'];
} else {
$delete_pic = "off";
}
if (strlen($response1)==0 &&
strlen($response2)==0 &&
$delete_pic != "on" &&
!is_uploaded_file($_FILES['picture']['tmp_name'])
) {
error_page("Your profile submission was empty.");
exit();
}
if ($delete_pic == "on") {
delete_user_pictures($profile->userid);
$profile->has_picture = false;
$profile->verification = 0;
}
$profile ? $hasPicture = $profile->has_picture: $hasPicture = false;
if (is_uploaded_file($_FILES['picture']['tmp_name'])) {
$hasPicture = true;
if ($profile) $profile->verification = 0;
// echo "<br>Name: " . $_FILES['picture']['name'];
// echo "<br>Type: " . $_FILES['picture']['type'];
// echo "<br>Size: " . $_FILES['picture']['size'];
// echo "<br>Temp name: " . $_FILES['picture']['tmp_name'];
$images = getImages($_FILES['picture']['tmp_name']);
// Write the original image file to disk.
// TODO: define a constant for image quality.
ImageJPEG($images[0], IMAGE_PATH . $user->id . '.jpg');
ImageJPEG($images[1], IMAGE_PATH . $user->id . '_sm.jpg');
}
$response1 = sanitize_html($response1);
$response2 = sanitize_html($response2);
if ($profile) {
$query = " response1 = '".boinc_real_escape_string($response1)."',"
." response2 = '".boinc_real_escape_string($response2)."',"
." language = '".boinc_real_escape_string($language)."',"
." has_picture = '$hasPicture',"
." verification = '$profile->verification'"
." WHERE userid = '$user->id'";
$result = BoincProfile::update_aux($query);
if (!$result) {
error_page("Couldn't update profile: database error");
}
} else {
$query = 'SET '
." userid = '$user->id',"
." language = '".boinc_real_escape_string($language)."',"
." response1 = '".boinc_real_escape_string($response1)."',"
." response2 = '".boinc_real_escape_string($response2)."',"
." has_picture = '$hasPicture',"
." verification=0";
$result = BoincProfile::insert($query);
if (!$result) {
error_page("Couldn't create profile: database error");
}
$user->update("has_profile=1");
}
page_head("Profile saved");
echo "
Congratulations!
Your profile was successfully entered into our database.<br><br>
<a href=view_profile.php?userid=$user->id>View your profile</a><br>
";
page_tail();
}
function show_profile_creation_page($user) {
$config = get_config();
$min_credit = parse_config($config, "<profile_min_credit>");
if ($min_credit && $user->expavg_credit < $min_credit) {
error_page(
"To prevent spam, an average credit of $min_credit or greater is required to create or edit a profile. We apologize for this inconvenience."
);
}
// If the user already has a profile,
// fill in the fields with their current values.
//
$profile = get_profile($user->id);
if (post_str("submit", true)) {
$privatekey = parse_config($config, "<recaptcha_private_key>");
if ($privatekey) {
$resp = recaptcha_check_answer($privatekey, $_SERVER["REMOTE_ADDR"],
$_POST["recaptcha_challenge_field"], $_POST["recaptcha_response_field"]
);
if (!$resp->is_valid) {
error_page("The reCAPTCHA wasn't entered correctly. Go back and try it again.<br>".
"(reCAPTCHA said: " . $resp->error . ")"
);
}
}
process_create_profile($user, $profile);
exit();
}
if ($profile) {
page_head("Edit your profile");
} else {
page_head("Create a profile");
}
echo "
<form action=", $_SERVER['PHP_SELF'], " method=\"POST\", ENCTYPE=\"multipart/form-data\">
";
start_table_noborder();
show_description();
show_questions($profile);
show_picture_option($profile);
show_submit();
end_table();
echo "</form>";
page_tail();
}
$user = get_logged_in_user(true);
show_profile_creation_page($user);

235
html/user/friend.php Normal file
View File

@ -0,0 +1,235 @@
<?php
// stuff related to "buddy lists"
require_once("../inc/forum_db.inc");
require_once("../inc/profile.inc");
// tell src:
// You are about to add X as a friend.
// We will notify X, who wil have to confirm that you are friends
// (add message here)
// add/cancel
//
// send PM or email to dest saying
// subject: X added you as a friend on Project
// X added you as a friend on Project.
// X says: Y
// To confirm this friend request, please visit:
// Z
//
// Thanks -- Project
//
// To control the emails you receive from Project, please visit W
//
// link goes to page:
// You have a friend request.
// (show picture)
// Name (conutry)
// You and X have friends in common:
// buttons: Confirm, Ignore
// small links: Send a message, report this person
// no rate-limiting mechanism
function send_request() {
}
// user has clicked "add to friends". Ask them if they really mean it.
//
function handle_add($user) {
$destid = get_int('userid');
if ($destid == $user->id) {
error_page("You can't be friends with yourself");
}
$destuser = BoincUser::lookup_id($destid);
if (!$destuser) error_page("No such user");
$friend = BoincFriend::lookup($user->id, $destid);
if ($friend) {
error_page("Friend request already exists");
}
page_head("Add friend");
echo "
<form method=post action=friend.php>
<input type=hidden name=userid value=$destid>
<input type=hidden name=action value=add_confirm>
You have asked to add <b>$destuser->name</b> as a friend.
We will notify <b>$destuser->name</b> and will ask him/her to
confirm that you are friends.
<p>
Add an optional message here:
<br>
<textarea name=message cols=64 rows=4></textarea>
<p>
<input type=submit value=OK>
<input type=submit value=Cancel>
";
page_tail();
}
// User really means it. Make DB entry and send notification
//
function handle_add_confirm($user) {
$destid = post_int('userid');
$destuser = BoincUser::lookup_id($destid);
if (!$destuser) error_page("No such user");
$msg = post_str('message', true);
if ($msg) $msg = strip_tags(process_user_text($msg));
$now = time();
$ret = BoincFriend::insert("(user_src, user_dest, message, create_time, reciprocated) values ($user->id, $destid, '$msg', $now, 0)");
if (!$ret) {
error_page("database error");
}
$ret = BoincNotify::insert("(userid, create_time, type, opaque) values ($destid, $now, ".NOTIFY_FRIEND_REQ.", $user->id)");
if (!$ret) {
error_page("Database error");
}
page_head("Friend request sent");
echo "We have notified <b>$destuser->name</b> of your request.";
page_tail();
}
// Show destination user the details of request, ask if they accept
//
function handle_query($user) {
$srcid = get_int('userid');
$srcuser = BoincUser::lookup_id($srcid);
if (!$srcuser) error_page("No such user");
$friend = BoincFriend::lookup($srcid, $user->id);
if (!$friend) error_page("Request not found");
page_head("Friend request");
$x = user_links($srcuser);
echo "
$x has added you as a friend.
If $srcuser->name is in fact your friend, please click Accept.
";
$img_url = profile_user_thumb_url($srcuser);
if ($img_url) {
echo "<p><img src=$img_url><p>\n";
}
if (strlen($friend->message)) {
echo "<p>$srcuser->name says: $friend->message<p>";
}
echo "
<p>
<a href=friend.php?action=accept&userid=$srcid>Accept</a> |
<a href=friend.php?action=ignore&userid=$srcid>Ignore</a>
";
page_tail();
}
// Here if they accepted
//
function handle_accept($user) {
$srcid = get_int('userid');
$srcuser = BoincUser::lookup_id($srcid);
if (!$srcuser) error_page("No such user");
$friend = BoincFriend::lookup($srcid, $user->id);
if (!$friend) {
error_page("No request");
}
$friend->update("reciprocated=1");
// "accept message" not implemented in interface yet
$msg = post_str('message', true);
if ($msg) $msg = strip_tags(process_user_text($msg));
$now = time();
$ret = BoincFriend::insert("(user_src, user_dest, message, create_time, reciprocated) values ($user->id, $srcid, '$msg', $now, 1)");
if (!$ret) {
error_page("database error");
}
$ret = BoincNotify::insert("(userid, create_time, type, opaque) values ($srcid, $now, ".NOTIFY_FRIEND_ACCEPT.", $user->id)");
if (!$ret) {
error_page("Database error");
}
$notify = BoincNotify::lookup($user->id, NOTIFY_FRIEND_REQ, $srcid);
if ($notify) {
$notify->delete();
} else {
echo "?? notification not found";
}
page_head("Friendship confirmed");
echo "Your friendship with <b>$srcuser->name</b> has been confirmed.";
page_tail();
}
// Here if they declined
//
function handle_ignore($user) {
$srcid = get_int('userid');
$srcuser = BoincUser::lookup_id($srcid);
if (!$srcuser) error_page("No such user");
$friend = BoincFriend::lookup($srcid, $user->id);
if (!$friend) {
error_page("No request");
}
$notify = BoincNotify::lookup($user->id, NOTIFY_FRIEND_REQ, $srcid);
if ($notify) {
$notify->delete();
} else {
echo "?? notification not found";
}
page_head("Friendship declined");
echo "You have declined friendship with <b>$srcuser->name</b>.";
page_tail();
}
// Here if initiator clicked on "confirmed" notification.
// Delete notification
//
function handle_accepted($user) {
$destid = get_int('userid');
$destuser = BoincUser::lookup_id($destid);
if (!$destuser) error_page("No such user");
$notify = BoincNotify::lookup($user->id, NOTIFY_FRIEND_ACCEPT, $destid);
if ($notify) {
$notify->delete();
} else {
echo "?? notification not found";
}
page_head("Friend confirmed");
echo "
You are now friends with $destuser->name.
";
page_tail();
}
// "home page" has Requests area
// (icon) N friend request(s)
$user = get_logged_in_user();
$action = get_str('action', true);
if (!$action) $action = post_str('action');
switch ($action) {
case 'add':
handle_add($user);
break;
case 'add_confirm':
handle_add_confirm($user);
break;
case 'query':
handle_query($user);
break;
case 'accept':
handle_accept($user);
break;
case 'accepted':
handle_accepted($user);
break;
case 'ignore':
handle_ignore($user);
break;
default:
error_page("unknown action");
}
?>

View File

@ -66,7 +66,8 @@ if ($format=="xml"){
error_page("No such user found - please check the ID and try again.");
}
// Output:
get_logged_in_user(true);
page_head("Account data for $user->name");
start_table();
show_user_summary_public($user);

View File

@ -220,7 +220,7 @@ int RPC_CLIENT::authorize(const char* passwd) {
n = snprintf(buf, sizeof(buf), "%s%s", nonce, passwd);
if (n >= (int)sizeof(buf)) return ERR_AUTHENTICATOR;
md5_block((const unsigned char*)buf, (int)strlen(buf), nonce_hash);
sprintf(buf, "<auth2>\n<nonce_hash>%s</nonce_hash>\n<auth2>\n", nonce_hash);
sprintf(buf, "<auth2>\n<nonce_hash>%s</nonce_hash>\n</auth2>\n", nonce_hash);
retval = rpc.do_rpc(buf);
if (retval) return retval;
while (rpc.fin.fgets(buf, 256)) {