|
1
|
|
|
<?php |
|
2
|
|
|
// This file is part of BOINC. |
|
3
|
|
|
// http://boinc.berkeley.edu |
|
4
|
|
|
// Copyright (C) 2008 University of California |
|
5
|
|
|
// |
|
6
|
|
|
// BOINC is free software; you can redistribute it and/or modify it |
|
7
|
|
|
// under the terms of the GNU Lesser General Public License |
|
8
|
|
|
// as published by the Free Software Foundation, |
|
9
|
|
|
// either version 3 of the License, or (at your option) any later version. |
|
10
|
|
|
// |
|
11
|
|
|
// BOINC is distributed in the hope that it will be useful, |
|
12
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
|
14
|
|
|
// See the GNU Lesser General Public License for more details. |
|
15
|
|
|
// |
|
16
|
|
|
// You should have received a copy of the GNU Lesser General Public License |
|
17
|
|
|
// along with BOINC. If not, see <http://www.gnu.org/licenses/>. |
|
18
|
|
|
|
|
19
|
|
|
require_once("../inc/util.inc"); |
|
20
|
|
|
require_once("../inc/boinc_db.inc"); |
|
21
|
|
|
require_once("../inc/forum_db.inc"); |
|
22
|
|
|
require_once("../inc/forum.inc"); |
|
23
|
|
|
require_once("../inc/sanitize_html.inc"); |
|
24
|
|
|
require_once("../inc/countries.inc"); |
|
25
|
|
|
require_once("../inc/credit.inc"); |
|
26
|
|
|
require_once("../inc/team_types.inc"); |
|
27
|
|
|
require_once("../inc/time.inc"); |
|
28
|
|
|
require_once("../inc/stats_sites.inc"); |
|
29
|
|
|
|
|
30
|
|
|
function team_search_form($params) { |
|
31
|
|
|
if (!$params) { |
|
32
|
|
|
$params = new StdClass; |
|
33
|
|
|
$params->keywords = ""; |
|
34
|
|
|
$params->country = ""; |
|
35
|
|
|
$params->type = ""; |
|
36
|
|
|
$params->active = false; |
|
37
|
|
|
} |
|
38
|
|
|
echo '<form name="form" action="team_search.php">'; |
|
39
|
|
|
start_table(); |
|
40
|
|
|
row2('<b>'.tra('Search criteria (use one or more)').'</b>', ''); |
|
41
|
|
|
row2( |
|
42
|
|
|
tra('Key words').'<br><small>'.tra('Find teams with these words in their names or descriptions').'</small>', |
|
43
|
|
|
'<input class="form-control" type="text" name="keywords" value="' . htmlspecialchars($params->keywords) . '">'); |
|
44
|
|
|
row2_init(tra('Country'), ''); |
|
45
|
|
|
echo '<select class="form-control" name="country"><option value="" selected>---</option>'; |
|
46
|
|
|
$country = $params->country; |
|
47
|
|
|
if (!$country || $country == 'None') $country = "XXX"; |
|
48
|
|
|
echo country_select_options($country); |
|
49
|
|
|
echo "</select></td></tr>\n"; |
|
50
|
|
|
row2(tra('Type of team'), team_type_select($params->type, true)); |
|
51
|
|
|
$checked = $params->active?"checked":""; |
|
52
|
|
|
row2(tra('Show only active teams'), "<input type=checkbox name=active $checked>"); |
|
53
|
|
|
row2("", "<input class=\"btn btn-primary\" type=submit name=submit value=\"".tra('Search')."\">"); |
|
54
|
|
|
end_table(); |
|
55
|
|
|
echo '</form>'; |
|
56
|
|
|
} |
|
57
|
|
|
|
|
58
|
|
|
function foundership_transfer_link($user, $team) { |
|
59
|
|
|
$now = time(); |
|
60
|
|
|
if ($team->ping_user == $user->id) { |
|
61
|
|
|
if (transfer_ok($team, $now)) { |
|
62
|
|
|
return tra('Requested by you, and founder response deadline has passed.').' |
|
63
|
|
|
<br> |
|
64
|
|
|
<a href="team_founder_transfer_form.php">'.tra('Complete foundership transfer').'</a>. |
|
65
|
|
|
'; |
|
66
|
|
|
} else { |
|
67
|
|
|
$deadline = date_str(transfer_ok_time($team)); |
|
68
|
|
|
return '<a href="team_founder_transfer_form.php">'.tra('Requested by you').'</a>; '.tra('founder response deadline is %1', $deadline); |
|
69
|
|
|
} |
|
70
|
|
|
} |
|
71
|
|
|
if (new_transfer_request_ok($team, $now)) { |
|
72
|
|
|
if ($team->userid == $user->id) { |
|
73
|
|
|
return tra('None'); |
|
74
|
|
|
} else { |
|
75
|
|
|
return '<a href="team_founder_transfer_form.php">'.tra('Initiate request').'</a>'; |
|
76
|
|
|
} |
|
77
|
|
|
} |
|
78
|
|
|
return '<a href="team_founder_transfer_form.php">'.tra('Deferred').'</a>'; |
|
79
|
|
|
} |
|
80
|
|
|
|
|
81
|
|
|
// $team is the team record with a bunch of additional data |
|
82
|
|
|
// (see team_display.php) |
|
83
|
|
|
// $user is viewer (not necessarily team founder) or null |
|
84
|
|
|
// |
|
85
|
|
|
function display_team_page($team, $user) { |
|
86
|
|
|
global $team_name_sites; |
|
87
|
|
|
page_head("$team->name"); |
|
88
|
|
|
|
|
89
|
|
|
echo sanitize_html($team->name_html); |
|
90
|
|
|
echo "<p>"; |
|
91
|
|
|
start_table(); |
|
92
|
|
|
row1(tra('Team info')); |
|
93
|
|
|
if (strlen($team->description)) { |
|
94
|
|
|
row2(tra('Description'), sanitize_html($team->description)); |
|
95
|
|
|
} |
|
96
|
|
|
row2("Created", date_str($team->create_time)); |
|
97
|
|
|
if (defined("SHOW_NONVALIDATED_TEAMS")) { |
|
98
|
|
|
$founder = $team->founder; |
|
99
|
|
|
row2("Founder email validated", $founder->email_validated?"Yes":"No (team will not be exported)"); |
|
100
|
|
|
} |
|
101
|
|
|
if (strlen($team->url)) {; |
|
102
|
|
|
if (strstr($team->url, "http://")) { |
|
103
|
|
|
$x = $team->url; |
|
104
|
|
|
} else { |
|
105
|
|
|
$x = "http://$team->url"; |
|
106
|
|
|
} |
|
107
|
|
|
row2(tra('Web site'), "<a href=$x>$x</a>"); |
|
108
|
|
|
} |
|
109
|
|
|
|
|
110
|
|
View Code Duplication |
if (!NO_STATS) { |
|
111
|
|
|
row2(tra('Total credit'), format_credit_large($team->total_credit)); |
|
112
|
|
|
row2(tra('Recent average credit'), format_credit_large($team->expavg_credit)); |
|
113
|
|
|
if (function_exists('project_team_credit')) { |
|
114
|
|
|
project_team_credit($team); |
|
115
|
|
|
} |
|
116
|
|
|
} |
|
117
|
|
|
show_badges_row(false, $team); |
|
118
|
|
|
if (!NO_STATS) { |
|
119
|
|
|
$x = ""; |
|
120
|
|
|
shuffle($team_name_sites); |
|
121
|
|
|
foreach ($team_name_sites as $t) { |
|
122
|
|
|
$url = $t[0]; |
|
123
|
|
|
$site_name = $t[1]; |
|
124
|
|
|
$encoding = $t[2]; |
|
125
|
|
|
if ($encoding == "hashlc") { |
|
126
|
|
|
$key = md5(strtolower($team->name)); |
|
127
|
|
|
} else if ($encoding == 'hash') { |
|
128
|
|
|
$key = md5($team->name); |
|
129
|
|
|
} else { |
|
130
|
|
|
$key = urlencode($team->name); |
|
131
|
|
|
} |
|
132
|
|
|
$x .= "<a href=$url".$key.">$site_name</a><br>\n"; |
|
133
|
|
|
} |
|
134
|
|
|
row2(tra('Cross-project stats'), $x); |
|
135
|
|
|
} |
|
136
|
|
|
row2(tra('Country'), $team->country); |
|
137
|
|
|
row2(tra('Type'), team_type_name($team->type)); |
|
138
|
|
|
|
|
139
|
|
|
if ($team->forum && is_forum_visible_to_user($team->forum, $user)) { |
|
140
|
|
|
$f = $team->forum; |
|
141
|
|
|
row2('<a href="team_forum.php?teamid='.$team->id.'">'.tra('Message board').'</a>', |
|
142
|
|
|
tra('Threads').': '.$f->threads.'<br>'.tra('Posts').': '.$f->posts.'<br>'.tra('Last post').': '.time_diff_str($f->timestamp, time()) |
|
143
|
|
|
); |
|
144
|
|
|
} |
|
145
|
|
|
if ($user) { |
|
146
|
|
|
if ($user->teamid != $team->id) { |
|
147
|
|
View Code Duplication |
if ($team->joinable) { |
|
148
|
|
|
$tokens = url_tokens($user->authenticator); |
|
149
|
|
|
row2("", |
|
150
|
|
|
'<a class="btn btn-success" href="team_join.php?'.$tokens.'&teamid='.$team->id.'">'.tra('Join this team').'</a> |
|
151
|
|
|
<br><p class=\"text-muted\">'.tra('Note: if \'OK to email\' is set in your project preferences, joining a team gives its founder access to your email address.').'</p>' |
|
152
|
|
|
); |
|
153
|
|
|
} else { |
|
154
|
|
|
row2(tra("Not accepting new members"), ""); |
|
155
|
|
|
} |
|
156
|
|
|
} |
|
157
|
|
|
if (($user->teamid == $team->id)) { |
|
158
|
|
|
if (($user->id == $team->userid)) { |
|
159
|
|
|
if ($team->ping_user) { |
|
160
|
|
|
$deadline = date_str(transfer_ok_time($team)); |
|
161
|
|
|
row2(tra('Foundership change requested'), |
|
162
|
|
|
'<a href="team_change_founder_form.php?teamid='.$team->id.'">'.tra('Respond by %1', $deadline).'</a>' |
|
163
|
|
|
); |
|
164
|
|
|
} |
|
165
|
|
|
} else { |
|
166
|
|
|
row2(tra('Team foundership change'), foundership_transfer_link($user, $team)); |
|
167
|
|
|
} |
|
168
|
|
|
} |
|
169
|
|
|
} |
|
170
|
|
|
row1(tra('Members')); |
|
171
|
|
|
row2(tra('Founder'), |
|
172
|
|
|
$team->founder?user_links($team->founder, BADGE_HEIGHT_MEDIUM):"---" |
|
173
|
|
|
); |
|
174
|
|
|
if (count($team->admins)) { |
|
175
|
|
|
$first = true; |
|
176
|
|
|
$x = ""; |
|
177
|
|
View Code Duplication |
foreach ($team->admins as $a) { |
|
178
|
|
|
if ($first) { |
|
179
|
|
|
$first = false; |
|
180
|
|
|
} else { |
|
181
|
|
|
$x .= " · "; |
|
182
|
|
|
} |
|
183
|
|
|
$x .= user_links($a, BADGE_HEIGHT_MEDIUM); |
|
184
|
|
|
} |
|
185
|
|
|
row2(tra('Admins'), $x); |
|
186
|
|
|
} |
|
187
|
|
|
$x = "0"; |
|
188
|
|
|
if (count($team->new_members)) { |
|
189
|
|
|
$first = true; |
|
190
|
|
|
$x = ""; |
|
191
|
|
View Code Duplication |
foreach ($team->new_members as $a) { |
|
192
|
|
|
if ($first) { |
|
193
|
|
|
$first = false; |
|
194
|
|
|
} else { |
|
195
|
|
|
$x .= " · "; |
|
196
|
|
|
} |
|
197
|
|
|
$x .= user_links($a, BADGE_HEIGHT_MEDIUM); |
|
198
|
|
|
} |
|
199
|
|
|
} |
|
200
|
|
|
row2(tra('New members in last day'), $x); |
|
201
|
|
|
row2(tra('Total members'), "$team->nusers (<a href=team_members.php?teamid=$team->id&offset=0&sort_by=expavg_credit>".tra('view')."</a>)"); |
|
202
|
|
|
if (!NO_STATS) { |
|
203
|
|
|
row2(tra('Active members'), "$team->nusers_active (<a href=team_members.php?teamid=$team->id&offset=0&sort_by=expavg_credit>".tra('view')."</a>)"); |
|
204
|
|
|
row2(tra('Members with credit'), "$team->nusers_worked (<a href=team_members.php?teamid=$team->id&offset=0&sort_by=total_credit>".tra('view')."</a>)"); |
|
205
|
|
|
} |
|
206
|
|
|
end_table(); |
|
207
|
|
|
} |
|
208
|
|
|
|
|
209
|
|
|
function display_team_members($team, $offset, $sort_by) { |
|
210
|
|
|
$n = 20; |
|
211
|
|
|
|
|
212
|
|
|
$admins = BoincTeamAdmin::enum("teamid=$team->id"); |
|
213
|
|
|
|
|
214
|
|
|
// there aren't indices to support sorting by credit. |
|
215
|
|
|
// set the following variable to disable sorted output. |
|
216
|
|
|
// (though since caching is generally used this shouldn't be needed) |
|
217
|
|
|
// |
|
218
|
|
|
$nosort = false; |
|
219
|
|
|
|
|
220
|
|
|
if ($sort_by == "total_credit") { |
|
221
|
|
|
$sort_clause = "total_credit desc"; |
|
222
|
|
|
} else { |
|
223
|
|
|
$sort_clause = "expavg_credit desc"; |
|
224
|
|
|
} |
|
225
|
|
|
|
|
226
|
|
|
start_table(); |
|
227
|
|
|
$x = array(); |
|
228
|
|
|
$a = array(); |
|
229
|
|
|
$x[] = tra('Name'); |
|
230
|
|
|
$a[] = ""; |
|
231
|
|
|
if (!NO_STATS) { |
|
232
|
|
|
if ($nosort) { |
|
233
|
|
|
$x[] = tra('Total credit'); |
|
234
|
|
|
$x[] = tra('Recent average credit'); |
|
235
|
|
|
} else { |
|
236
|
|
View Code Duplication |
if ($sort_by == "total_credit") { |
|
237
|
|
|
$x[] = tra('Total credit'); |
|
238
|
|
|
} else { |
|
239
|
|
|
$x[] = "<href=team_members.php?teamid=$team->id&sort_by=total_credit&offset=$offset>".tra('Total credit')."</a>"; |
|
240
|
|
|
} |
|
241
|
|
View Code Duplication |
if ($sort_by == "expavg_credit") { |
|
242
|
|
|
$x[] = tra('Recent average credit'); |
|
243
|
|
|
} else { |
|
244
|
|
|
$x[] = "<href=team_members.php?teamid=$team->id&sort_by=expavg_credit&offset=$offset>".tra('Recent average credit').'</a>'; |
|
245
|
|
|
} |
|
246
|
|
|
} |
|
247
|
|
|
$a[] = ALIGN_RIGHT; |
|
248
|
|
|
$a[] = ALIGN_RIGHT; |
|
249
|
|
|
} |
|
250
|
|
|
|
|
251
|
|
|
$x[] = tra('Country'); |
|
252
|
|
|
$a[] = ""; |
|
253
|
|
|
row_heading_array($x, $a); |
|
254
|
|
|
|
|
255
|
|
|
$cache_args = "teamid=".$team->id."&mosort=".$nosort."&order=".$sort_clause."&limit=".$offset."_".$n; |
|
256
|
|
|
$users = unserialize(get_cached_data(TEAM_PAGE_TTL, $cache_args)); |
|
257
|
|
|
if (!$users) { |
|
258
|
|
|
if ($nosort) { |
|
259
|
|
|
$users = BoincUser::enum("teamid=$team->id limit $offset,$n"); |
|
260
|
|
|
} else { |
|
261
|
|
|
$users = BoincUser::enum("teamid=$team->id order by $sort_clause limit $offset,$n"); |
|
262
|
|
|
} |
|
263
|
|
|
set_cached_data(TEAM_PAGE_TTL, serialize($users), $cache_args); |
|
264
|
|
|
} |
|
265
|
|
|
|
|
266
|
|
|
$j = $offset + 1; |
|
267
|
|
|
foreach ($users as $user) { |
|
268
|
|
|
$user_total_credit = format_credit_large($user->total_credit); |
|
269
|
|
|
$user_expavg_credit = format_credit($user->expavg_credit); |
|
270
|
|
|
$x = user_links($user, BADGE_HEIGHT_MEDIUM); |
|
271
|
|
|
if ($user->id == $team->userid) { |
|
272
|
|
|
$x .= ' ['.tra('Founder').']'; |
|
273
|
|
|
} else if (is_team_admin_aux($user, $admins)) { |
|
274
|
|
|
$x .= ' ['.tra('Admin').']'; |
|
275
|
|
|
} |
|
276
|
|
|
echo "<tr class=row1> |
|
277
|
|
|
<td align=left>$j) $x |
|
278
|
|
|
</td>"; |
|
279
|
|
|
if (!NO_STATS) { |
|
280
|
|
|
echo " |
|
281
|
|
|
<td align=right>$user_total_credit</td> |
|
282
|
|
|
<td align=right>$user_expavg_credit</td> |
|
283
|
|
|
"; |
|
284
|
|
|
} |
|
285
|
|
|
echo " |
|
286
|
|
|
<td>$user->country</td> |
|
287
|
|
|
</tr> |
|
288
|
|
|
"; |
|
289
|
|
|
$j++; |
|
290
|
|
|
} |
|
291
|
|
|
echo "</table>"; |
|
292
|
|
|
|
|
293
|
|
|
if ($offset > 0) { |
|
294
|
|
|
$new_offset = $offset - $n; |
|
295
|
|
|
echo "<a href=team_members.php?teamid=$team->id&sort_by=$sort_by&offset=$new_offset>".tra('Previous %1', $n)."</a> · "; |
|
296
|
|
|
} |
|
297
|
|
|
if ($j == $offset + $n + 1) { |
|
298
|
|
|
$new_offset = $offset + $n; |
|
299
|
|
|
echo "<a href=team_members.php?teamid=$team->id&sort_by=$sort_by&offset=$new_offset>".tra('Next %1', $n)."</a>"; |
|
300
|
|
|
} |
|
301
|
|
|
} |
|
302
|
|
|
|
|
303
|
|
|
// check that the team exists |
|
304
|
|
|
// |
|
305
|
|
|
function require_team($team) { |
|
306
|
|
|
if (!$team) { |
|
307
|
|
|
error_page(tra('No such team.')); |
|
308
|
|
|
} |
|
309
|
|
|
} |
|
310
|
|
|
|
|
311
|
|
|
function is_team_founder($user, $team) { |
|
312
|
|
|
return $user->id == $team->userid; |
|
313
|
|
|
} |
|
314
|
|
|
|
|
315
|
|
|
// check that the user is founder of the team |
|
316
|
|
|
// |
|
317
|
|
|
function require_founder_login($user, $team) { |
|
318
|
|
|
require_team($team); |
|
319
|
|
|
if ($user->id != $team->userid) { |
|
320
|
|
|
error_page(tra('This operation requires foundership.')); |
|
321
|
|
|
} |
|
322
|
|
|
} |
|
323
|
|
|
|
|
324
|
|
|
function is_team_admin($user, $team) { |
|
325
|
|
|
if (!$user) return false; |
|
326
|
|
|
if ($user->id == $team->userid) return true; |
|
327
|
|
|
$admin = BoincTeamAdmin::lookup($team->id, $user->id); |
|
328
|
|
|
if ($admin) return true; |
|
329
|
|
|
return false; |
|
330
|
|
|
} |
|
331
|
|
|
|
|
332
|
|
|
// use this when you're displaying a long list of users |
|
333
|
|
|
// and don't want to do a lookup for each one |
|
334
|
|
|
// |
|
335
|
|
|
function is_team_admin_aux($user, $admins) { |
|
336
|
|
|
foreach ($admins as $a) { |
|
337
|
|
|
if ($a->userid == $user->id) return true; |
|
338
|
|
|
} |
|
339
|
|
|
return false; |
|
340
|
|
|
} |
|
341
|
|
|
|
|
342
|
|
|
function require_admin($user, $team) { |
|
343
|
|
|
if (!is_team_admin($user, $team)) { |
|
344
|
|
|
error_page(tra('This operation requires team admin privileges')); |
|
345
|
|
|
} |
|
346
|
|
|
} |
|
347
|
|
|
|
|
348
|
|
|
function new_member_list($teamid) { |
|
349
|
|
|
$new_members = array(); |
|
350
|
|
|
$yesterday = time() - 86400; |
|
351
|
|
|
$deltas = BoincTeamDelta::enum("teamid=$teamid and timestamp>$yesterday and joining=1 group by userid"); |
|
352
|
|
|
if (count($deltas)) { |
|
353
|
|
|
foreach ($deltas as $delta) { |
|
354
|
|
|
$u = BoincUser::lookup_id($delta->userid); |
|
355
|
|
|
if ($u->teamid == $teamid) { |
|
356
|
|
|
$new_members[] = $u; // they might have later quit |
|
357
|
|
|
} |
|
358
|
|
|
} |
|
359
|
|
|
} |
|
360
|
|
|
return $new_members; |
|
361
|
|
|
} |
|
362
|
|
|
|
|
363
|
|
|
function admin_list($teamid) { |
|
364
|
|
|
$u = array(); |
|
365
|
|
|
$admins = BoincTeamAdmin::enum("teamid=$teamid"); |
|
366
|
|
|
foreach ($admins as $admin) { |
|
367
|
|
|
$user = BoincUser::lookup_id($admin->userid); |
|
368
|
|
|
$u[] = $user; |
|
369
|
|
|
} |
|
370
|
|
|
return $u; |
|
371
|
|
|
} |
|
372
|
|
|
|
|
373
|
|
|
function team_table_start($sort_by, $type_url) { |
|
374
|
|
|
$x = array(); |
|
375
|
|
|
$x[] = tra('Rank'); |
|
376
|
|
|
$x[] = tra('Name'); |
|
377
|
|
|
$x[] = tra('Members'); |
|
378
|
|
|
$a = array("", "", ALIGN_RIGHT); |
|
379
|
|
|
if (!NO_STATS) { |
|
380
|
|
|
if ($sort_by == "total_credit") { |
|
381
|
|
|
$x[] = "<a href=top_teams.php?sort_by=expavg_credit".$type_url.">".tra('Recent average credit')."</a>"; |
|
382
|
|
|
$x[] = tra('Total credit'); |
|
383
|
|
|
} else { |
|
384
|
|
|
$x[] = tra('Recent average credit'); |
|
385
|
|
|
$x[] = "<a href=top_teams.php?sort_by=total_credit".$type_url.">".tra('Total credit')."</a>"; |
|
386
|
|
|
} |
|
387
|
|
|
$a[] = ALIGN_RIGHT; |
|
388
|
|
|
$a[] = ALIGN_RIGHT; |
|
389
|
|
|
} |
|
390
|
|
|
$x[] = tra('Country'); |
|
391
|
|
|
$x[] = tra("Type"); |
|
392
|
|
|
$a[] = ""; |
|
393
|
|
|
$a[] = ""; |
|
394
|
|
|
|
|
395
|
|
|
row_heading_array($x, $a); |
|
396
|
|
|
} |
|
397
|
|
|
|
|
398
|
|
|
function team_links($team) { |
|
399
|
|
|
$b = badges_string(false, $team, BADGE_HEIGHT_MEDIUM); |
|
400
|
|
|
return "<a href=team_display.php?teamid=$team->id>$team->name</a> $b"; |
|
401
|
|
|
} |
|
402
|
|
|
|
|
403
|
|
|
function show_team_row($team, $i) { |
|
404
|
|
|
$team_expavg_credit = format_credit_large($team->expavg_credit); |
|
405
|
|
|
$team_total_credit = format_credit_large($team->total_credit); |
|
406
|
|
|
echo "<tr> |
|
407
|
|
|
<td>$i</td> |
|
408
|
|
|
<td>".team_links($team)."</td> |
|
409
|
|
|
<td align=right>".$team->nusers."</td> |
|
410
|
|
|
"; |
|
411
|
|
|
if (!NO_STATS) { |
|
412
|
|
|
echo " |
|
413
|
|
|
<td align=right>$team_expavg_credit</td> |
|
414
|
|
|
<td align=right>$team_total_credit</td> |
|
415
|
|
|
"; |
|
416
|
|
|
} |
|
417
|
|
|
echo " |
|
418
|
|
|
<td>$team->country</td> |
|
419
|
|
|
<td>".team_type_name($team->type)."</td> |
|
420
|
|
|
</tr> |
|
421
|
|
|
"; |
|
422
|
|
|
} |
|
423
|
|
|
|
|
424
|
|
|
function user_join_team($team, $user) { |
|
425
|
|
|
user_quit_team($user); |
|
426
|
|
|
$res = $user->update("teamid=$team->id"); |
|
427
|
|
|
if ($res) { |
|
428
|
|
|
$now = time(); |
|
429
|
|
|
BoincTeamDelta::insert("(userid, teamid, timestamp, joining, total_credit) values ($user->id, $team->id, $now, 1, $user->total_credit)"); |
|
430
|
|
|
return true; |
|
431
|
|
|
} |
|
432
|
|
|
return false; |
|
433
|
|
|
} |
|
434
|
|
|
|
|
435
|
|
|
function user_quit_team($user) { |
|
436
|
|
|
if (!$user->teamid) return; |
|
437
|
|
|
$user->update("teamid=0"); |
|
438
|
|
|
$team = BoincTeam::lookup_id($user->teamid); |
|
439
|
|
|
if ($team && $team->ping_user==$user->id) { |
|
440
|
|
|
$team->update("ping_user=-ping_user"); |
|
441
|
|
|
} |
|
442
|
|
|
BoincTeamAdmin::delete("teamid=$user->teamid and userid=$user->id"); |
|
443
|
|
|
$now = time(); |
|
444
|
|
|
BoincTeamDelta::insert("(userid, teamid, timestamp, joining, total_credit) values ($user->id, $user->teamid, $now, 0, $user->total_credit)"); |
|
445
|
|
|
} |
|
446
|
|
|
|
|
447
|
|
|
function user_erase_team_owner($user) { |
|
448
|
|
|
if ($user->teamid) { |
|
449
|
|
|
$team = BoincTeam::lookup_id($user->teamid); |
|
450
|
|
|
if ($team && $team.userid == $user->id) { |
|
451
|
|
|
$team->update("userid=0"); |
|
452
|
|
|
} |
|
453
|
|
|
} |
|
454
|
|
|
} |
|
455
|
|
|
|
|
456
|
|
|
function user_erase_team_delta($user) { |
|
457
|
|
|
BoincTeamDelta::delete_for_user($user->id); |
|
458
|
|
|
} |
|
459
|
|
|
|
|
460
|
|
|
function team_edit_form($team, $label, $url) { |
|
461
|
|
|
global $team_types, $recaptcha_public_key; |
|
462
|
|
|
echo "<form method=post action=$url>\n"; |
|
463
|
|
|
if ($team) { |
|
464
|
|
|
echo "<input type=hidden name=teamid value=$team->id>\n"; |
|
465
|
|
|
if ($team->seti_id) { |
|
466
|
|
|
echo "<p class=\"text-danger\">".tra("WARNING: this is a BOINC-wide team. If you make changes here, they will soon be overwritten. Edit the %1 BOINC-wide team %2 instead.", "<a href=https://boinc.berkeley.edu/teams/>", "</a>") |
|
467
|
|
|
."</p>"; |
|
468
|
|
|
} |
|
469
|
|
|
} |
|
470
|
|
|
echo ' |
|
471
|
|
|
<p> |
|
472
|
|
|
'.tra('%1 Privacy note %2: if you create a team, your project preferences (resource share, graphics preferences) will be visible to the public.', '<b>', '</b>').' |
|
473
|
|
|
<p> |
|
474
|
|
|
'; |
|
475
|
|
|
start_table(); |
|
476
|
|
|
row2(tra('Team name, text version').' |
|
477
|
|
|
<br><p class=\"text-muted\">'.tra('Don\'t use HTML tags.').'</p>', |
|
478
|
|
|
'<input class="form-control" name="name" type="text" size="50" value="'.($team?$team->name:"").'">' |
|
479
|
|
|
); |
|
480
|
|
|
row2(tra('Team name, HTML version').' |
|
481
|
|
|
<br><p class=\"text-muted\"> |
|
482
|
|
|
'.tra('You may use %1 limited HTML tags %2.', '<a href="html.php" target="_new">', '</a>').' |
|
483
|
|
|
'.tra('If you don\'t know HTML, leave this box blank.').'</p>', |
|
484
|
|
|
'<input class="form-control" name="name_html" type="text" size="50" value="'.str_replace('"',"'",($team?$team->name_html:"")).'">' |
|
485
|
|
|
); |
|
486
|
|
|
row2(tra('URL of team web page, if any').':<br><font size=-2>('.tra('without "http://"').') |
|
487
|
|
|
'.tra('This URL will be linked to from the team\'s page on this site.'), |
|
488
|
|
|
'<input class="form-control" type="text" name="url" size="60" value="'.($team?$team->url:"").'">' |
|
489
|
|
|
); |
|
490
|
|
|
row2(tra('Description of team').': |
|
491
|
|
|
<br><p class=\"text-muted\"> |
|
492
|
|
|
'.tra('You may use %1 limited HTML tags %2.', '<a href="html.php" target="_new">', '</a>').' |
|
493
|
|
|
</p>', |
|
494
|
|
|
'<textarea class="form-control" name="description" rows=10>'.($team?$team->description:"").'</textarea>' |
|
495
|
|
|
); |
|
496
|
|
|
|
|
497
|
|
|
row2(tra('Type of team').':', team_type_select($team?$team->type:null)); |
|
498
|
|
|
|
|
499
|
|
|
row2_init(tra('Country'), |
|
500
|
|
|
'<select class="form-control" name="country">' |
|
501
|
|
|
); |
|
502
|
|
|
echo country_select_options($team?$team->country:null); |
|
503
|
|
|
|
|
504
|
|
|
echo "</select></td></tr>\n"; |
|
505
|
|
|
$x = (!$team || $team->joinable)?"checked":""; |
|
506
|
|
|
row2(tra("Accept new members?"), "<input type=checkbox name=joinable $x>"); |
|
507
|
|
|
// Check if we're using reCaptcha to prevent spam accounts |
|
508
|
|
|
// |
|
509
|
|
|
if (!$team && $recaptcha_public_key) { |
|
510
|
|
|
row2( |
|
511
|
|
|
"", |
|
512
|
|
|
boinc_recaptcha_get_html($recaptcha_public_key) |
|
513
|
|
|
); |
|
514
|
|
|
} |
|
515
|
|
|
row2("", |
|
516
|
|
|
"<input class=\"btn btn-success\" type=submit name=new value='$label'>" |
|
517
|
|
|
); |
|
518
|
|
|
end_table(); |
|
519
|
|
|
echo "</form>\n"; |
|
520
|
|
|
} |
|
521
|
|
|
|
|
522
|
|
|
// decay a team's average credit |
|
523
|
|
|
// |
|
524
|
|
View Code Duplication |
function team_decay_credit($team) { |
|
|
|
|
|
|
525
|
|
|
$avg = $team->expavg_credit; |
|
526
|
|
|
$avg_time = $team->expavg_time; |
|
527
|
|
|
$now = time(0); |
|
528
|
|
|
update_average($now, 0, 0, $avg, $avg_time); |
|
529
|
|
|
$team->update("expavg_credit=$avg, expavg_time=$now"); |
|
530
|
|
|
|
|
531
|
|
|
} |
|
532
|
|
|
// if the team hasn't received new credit for ndays, |
|
533
|
|
|
// decay its average and return true |
|
534
|
|
|
// |
|
535
|
|
|
function team_inactive_ndays($team, $ndays) { |
|
536
|
|
|
$diff = time() - $team->expavg_time; |
|
537
|
|
|
if ($diff > $ndays*86400) { |
|
538
|
|
|
team_decay_credit($team); |
|
539
|
|
|
return true; |
|
540
|
|
|
} |
|
541
|
|
|
return false; |
|
542
|
|
|
} |
|
543
|
|
|
|
|
544
|
|
|
function team_count_members($teamid) { |
|
545
|
|
|
return BoincUser::count("teamid=$teamid"); |
|
546
|
|
|
} |
|
547
|
|
|
|
|
548
|
|
|
// These functions determine the rules for foundership transfer, namely: |
|
549
|
|
|
// - A transfer request is allowed if either: |
|
550
|
|
|
// - there is no active request, and it's been at least 60 days |
|
551
|
|
|
// since the last request (this protects the founder from |
|
552
|
|
|
// being bombarded with frequest requests) |
|
553
|
|
|
// - there's an active request older than 90 days |
|
554
|
|
|
// (this lets a 2nd requester eventually get a chance) |
|
555
|
|
|
// - Suppose someone (X) requests foundership at time T. |
|
556
|
|
|
// An email is sent to the founder (Y). |
|
557
|
|
|
// The request is "active" (ping_user is set to X's ID) |
|
558
|
|
|
// - If Y declines the change, an email is sent to X, |
|
559
|
|
|
// and the request is cleared. |
|
560
|
|
|
// - If Y accepts the change, an email is sent to X |
|
561
|
|
|
// and the request is cleared. |
|
562
|
|
|
// - After T + 60 days, X can become founder |
|
563
|
|
|
// - After T + 90 days, new requests are allowed even if there's |
|
564
|
|
|
// an active request, i.e. after the 60 days elapse X has another |
|
565
|
|
|
// 30 days to assume foundership before someone elase can request it |
|
566
|
|
|
// |
|
567
|
|
|
function new_transfer_request_ok($team, $now) { |
|
568
|
|
View Code Duplication |
if ($team->ping_user <= 0) { |
|
569
|
|
|
if ($team->ping_time < $now - 60 * 86400) { |
|
570
|
|
|
return true; |
|
571
|
|
|
} |
|
572
|
|
|
return false; |
|
573
|
|
|
} |
|
574
|
|
|
if ($team->ping_time < $now - 90 * 86400) { |
|
575
|
|
|
return true; |
|
576
|
|
|
} |
|
577
|
|
|
return false; |
|
578
|
|
|
} |
|
579
|
|
|
|
|
580
|
|
|
// the time at which we can actually change foundership |
|
581
|
|
|
// if the founder hasn't responded |
|
582
|
|
|
// |
|
583
|
|
|
function transfer_ok_time($team) { |
|
584
|
|
|
return $team->ping_time + 60*86400; |
|
585
|
|
|
} |
|
586
|
|
|
|
|
587
|
|
|
function transfer_ok($team, $now) { |
|
588
|
|
|
if ($now > transfer_ok_time($team)) return true; |
|
589
|
|
|
return false; |
|
590
|
|
|
} |
|
591
|
|
|
|
|
592
|
|
|
// Make a team; args are untrusted, so cleanse and validate them |
|
593
|
|
|
// |
|
594
|
|
|
function make_team( |
|
595
|
|
|
$userid, $name, $url, $type, $name_html, $description, $country |
|
596
|
|
|
) { |
|
597
|
|
|
$name = BoincDb::escape_string(sanitize_tags($name)); |
|
598
|
|
|
if (strlen($name) == 0) return null; |
|
599
|
|
|
$name_lc = strtolower($name); |
|
600
|
|
|
$url = BoincDb::escape_string(sanitize_tags($url)); |
|
601
|
|
|
if (strstr($url, "http://")) { |
|
602
|
|
|
$url = substr($url, 7); |
|
603
|
|
|
} |
|
604
|
|
|
$name_html = BoincDb::escape_string($name_html); |
|
605
|
|
|
$description = BoincDb::escape_string($description); |
|
606
|
|
|
if (!is_valid_country($country)) { |
|
607
|
|
|
$country = tra('None'); |
|
608
|
|
|
} |
|
609
|
|
|
$country = BoincDb::escape_string($country); // for Cote d'Ivoire |
|
610
|
|
|
|
|
611
|
|
|
$clause = sprintf( |
|
612
|
|
|
"(userid, create_time, name, name_lc, url, type, name_html, description, country, nusers, expavg_time) values(%d, %d, '%s', '%s', '%s', %d, '%s', '%s', '%s', %d, unix_timestamp())", |
|
613
|
|
|
$userid, |
|
614
|
|
|
time(), |
|
615
|
|
|
$name, |
|
616
|
|
|
$name_lc, |
|
617
|
|
|
$url, |
|
618
|
|
|
$type, |
|
619
|
|
|
$name_html, |
|
620
|
|
|
$description, |
|
621
|
|
|
$country, |
|
622
|
|
|
0 |
|
623
|
|
|
); |
|
624
|
|
|
$id = BoincTeam::insert($clause); |
|
625
|
|
|
if ($id) { |
|
626
|
|
|
return BoincTeam::lookup_id($id); |
|
627
|
|
|
} else { |
|
628
|
|
|
return null; |
|
629
|
|
|
} |
|
630
|
|
|
} |
|
631
|
|
|
|
|
632
|
|
|
$cvs_version_tracker[]="\$Id$"; //Generated automatically - do not edit |
|
633
|
|
|
|
|
634
|
|
|
?> |
|
635
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.